From 7fa5b8dcb59957c634b8d706f28c03456c62c5c6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 12 Aug 2022 11:38:43 +0200 Subject: [PATCH] Turn CGLIB patch into full CGLIB fork in spring-core Closes gh-28955 --- gradle/ide.gradle | 2 +- spring-core/spring-core.gradle | 49 +- .../org/springframework/cglib/core/Block.java | 49 + .../cglib/core/ClassEmitter.java | 282 +++++ .../cglib/core/ClassGenerator.java | 22 + .../springframework/cglib/core/ClassInfo.java | 47 + .../cglib/core/ClassNameReader.java | 65 ++ .../cglib/core/ClassesKey.java | 46 + .../cglib/core/CodeEmitter.java | 870 ++++++++++++++++ .../cglib/core/CodeGenerationException.java | 33 + .../cglib/core/CollectionUtils.java | 77 ++ .../springframework/cglib/core/Constants.java | 73 ++ .../springframework/cglib/core/Converter.java | 21 + .../cglib/core/Customizer.java | 28 + .../cglib/core/DebuggingClassWriter.java | 108 ++ .../cglib/core/DefaultGeneratorStrategy.java | 47 + .../cglib/core/DefaultNamingPolicy.java | 71 ++ .../cglib/core/DuplicatesPredicate.java | 165 +++ .../springframework/cglib/core/EmitUtils.java | 961 ++++++++++++++++++ .../cglib/core/FieldTypeCustomizer.java | 23 + .../cglib/core/GeneratorStrategy.java | 44 + .../cglib/core/HashCodeCustomizer.java | 12 + .../cglib/core/KeyFactoryCustomizer.java | 7 + .../org/springframework/cglib/core/Local.java | 37 + .../cglib/core/LocalVariablesSorter.java | 158 +++ .../cglib/core/MethodInfo.java | 47 + .../cglib/core/MethodInfoTransformer.java | 37 + .../cglib/core/NamingPolicy.java | 43 + .../cglib/core/ObjectSwitchCallback.java | 24 + .../springframework/cglib/core/Predicate.java | 21 + .../cglib/core/ProcessArrayCallback.java | 22 + .../cglib/core/ProcessSwitchCallback.java | 23 + .../cglib/core/RejectModifierPredicate.java | 30 + .../springframework/cglib/core/Signature.java | 73 ++ .../cglib/core/TinyBitSet.java | 84 ++ .../cglib/core/Transformer.java | 20 + .../springframework/cglib/core/TypeUtils.java | 422 ++++++++ .../cglib/core/VisibilityPredicate.java | 53 + .../cglib/core/WeakCacheKey.java | 42 + .../core/internal/CustomizerRegistry.java | 49 + .../cglib/core/internal/Function.java | 5 + .../cglib/core/internal/LoadingCache.java | 87 ++ .../cglib/proxy/BridgeMethodResolver.java | 127 +++ .../springframework/cglib/proxy/Callback.java | 29 + .../cglib/proxy/CallbackFilter.java | 46 + .../cglib/proxy/CallbackGenerator.java | 37 + .../cglib/proxy/CallbackHelper.java | 99 ++ .../cglib/proxy/CallbackInfo.java | 117 +++ .../cglib/proxy/Dispatcher.java | 30 + .../cglib/proxy/DispatcherGenerator.java | 66 ++ .../springframework/cglib/proxy/Factory.java | 80 ++ .../cglib/proxy/FixedValue.java | 35 + .../cglib/proxy/FixedValueGenerator.java | 43 + .../cglib/proxy/InterfaceMaker.java | 119 +++ .../cglib/proxy/InvocationHandler.java | 35 + .../proxy/InvocationHandlerGenerator.java | 65 ++ .../cglib/proxy/LazyLoader.java | 30 + .../cglib/proxy/LazyLoaderGenerator.java | 89 ++ .../cglib/proxy/MethodInterceptor.java | 42 + .../proxy/MethodInterceptorGenerator.java | 238 +++++ .../springframework/cglib/proxy/Mixin.java | 243 +++++ .../cglib/proxy/MixinBeanEmitter.java | 39 + .../cglib/proxy/MixinEmitter.java | 94 ++ .../cglib/proxy/MixinEverythingEmitter.java | 50 + .../org/springframework/cglib/proxy/NoOp.java | 28 + .../cglib/proxy/NoOpGenerator.java | 44 + .../springframework/cglib/proxy/Proxy.java | 102 ++ .../cglib/proxy/ProxyRefDispatcher.java | 31 + .../proxy/UndeclaredThrowableException.java | 37 + .../cglib/reflect/ConstructorDelegate.java | 124 +++ .../cglib/reflect/FastClass.java | 208 ++++ .../cglib/reflect/FastClassEmitter.java | 227 +++++ .../cglib/reflect/FastConstructor.java | 47 + .../cglib/reflect/FastMember.java | 66 ++ .../cglib/reflect/FastMethod.java | 64 ++ .../cglib/reflect/MethodDelegate.java | 269 +++++ .../cglib/reflect/MulticastDelegate.java | 180 ++++ .../AbstractClassFilterTransformer.java | 85 ++ .../cglib/transform/AbstractClassLoader.java | 112 ++ .../transform/AbstractClassTransformer.java | 29 + .../cglib/transform/AnnotationVisitorTee.java | 61 ++ .../transform/ClassEmitterTransformer.java | 21 + .../cglib/transform/ClassFilter.java | 27 + .../transform/ClassFilterTransformer.java | 31 + .../cglib/transform/ClassReaderGenerator.java | 41 + .../cglib/transform/ClassTransformer.java | 29 + .../transform/ClassTransformerChain.java | 56 + .../transform/ClassTransformerFactory.java | 20 + .../cglib/transform/ClassTransformerTee.java | 32 + .../cglib/transform/ClassVisitorTee.java | 104 ++ .../cglib/transform/FieldVisitorTee.java | 53 + .../cglib/transform/MethodFilter.java | 23 + .../transform/MethodFilterTransformer.java | 43 + .../cglib/transform/MethodVisitorTee.java | 189 ++++ .../transform/TransformingClassGenerator.java | 35 + .../transform/TransformingClassLoader.java | 34 + .../impl/AbstractInterceptFieldCallback.java | 42 + .../impl/AccessFieldTransformer.java | 64 ++ .../impl/AddDelegateTransformer.java | 120 +++ .../transform/impl/AddInitTransformer.java | 63 ++ .../impl/AddPropertyTransformer.java | 48 + .../impl/AddStaticInitTransformer.java | 49 + .../cglib/transform/impl/FieldProvider.java | 33 + .../impl/FieldProviderTransformer.java | 209 ++++ .../impl/InterceptFieldCallback.java | 42 + .../transform/impl/InterceptFieldEnabled.java | 21 + .../transform/impl/InterceptFieldFilter.java | 23 + .../impl/InterceptFieldTransformer.java | 210 ++++ .../impl/UndeclaredThrowableStrategy.java | 61 ++ .../impl/UndeclaredThrowableTransformer.java | 61 ++ .../cglib/util/ParallelSorter.java | 295 ++++++ .../cglib/util/ParallelSorterEmitter.java | 100 ++ .../cglib/util/SorterTemplate.java | 173 ++++ .../cglib/util/StringSwitcher.java | 155 +++ 114 files changed, 10304 insertions(+), 49 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Block.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ClassGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Constants.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Converter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Customizer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/DebuggingClassWriter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/FieldTypeCustomizer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/HashCodeCustomizer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/KeyFactoryCustomizer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Local.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/MethodInfoTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/NamingPolicy.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ObjectSwitchCallback.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Predicate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ProcessArrayCallback.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/ProcessSwitchCallback.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Signature.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/TinyBitSet.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/Transformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/WeakCacheKey.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/internal/Function.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/Callback.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/Dispatcher.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/FixedValue.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandler.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoader.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/Mixin.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/NoOp.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/ProxyRefDispatcher.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/MethodDelegate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassEmitterTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassFilterTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerFactory.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/MethodFilter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassGenerator.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassLoader.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/AccessFieldTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/AddInitTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProviderTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldEnabled.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldFilter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/util/ParallelSorterEmitter.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java create mode 100644 spring-core/src/main/java/org/springframework/cglib/util/StringSwitcher.java diff --git a/gradle/ide.gradle b/gradle/ide.gradle index cfc3867312..71a03ad688 100644 --- a/gradle/ide.gradle +++ b/gradle/ide.gradle @@ -30,7 +30,7 @@ eclipse.classpath.file.whenMerged { classpath -> } // Remove any remaining direct depencencies on JARs in the build/libs folder - // except Spring's spring-cglib-repack and spring-objenesis-repack JARs. + // except the repack JARs. classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) } } diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index bb776c8383..351c0d7e08 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -5,43 +5,15 @@ description = "Spring Core" apply plugin: "kotlin" -// spring-core includes asm, javapoet and repackages cglib, inlining all into the -// spring-core jar. cglib itself depends on asm and is therefore further transformed by -// the ShadowJar task to depend on org.springframework.asm; this avoids including two -// different copies of asm. -def cglibVersion = "3.3.0" def javapoetVersion = "1.13.0" def objenesisVersion = "3.2" configurations { - cglib javapoet objenesis graalvm } -task cglibRepackJar(type: ShadowJar) { - archiveBaseName = 'spring-cglib-repack' - archiveVersion = cglibVersion - configurations = [project.configurations.cglib] - relocate('net.sf.cglib', 'org.springframework.cglib') - relocate('org.objectweb.asm', 'org.springframework.asm') -} - -task cglibSource(type: ShadowSource) { - configurations = [project.configurations.cglib] - relocate('net.sf.cglib', 'org.springframework.cglib') - relocate('org.objectweb.asm', 'org.springframework.asm') - outputDirectory = file("build/shadow-source/cglib") -} - -task cglibSourceJar(type: Jar) { - archiveBaseName = 'spring-cglib-repack' - archiveVersion = cglibVersion - archiveClassifier = 'sources' - from cglibSource -} - task javapoetRepackJar(type: ShadowJar) { archiveBaseName = 'spring-javapoet-repack' archiveVersion = javapoetVersion @@ -83,11 +55,9 @@ task objenesisSourceJar(type: Jar) { } dependencies { - cglib("cglib:cglib:${cglibVersion}@jar") javapoet("com.squareup:javapoet:${javapoetVersion}@jar") objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") graalvm(project(path: ":graalvm-feature", configuration: 'classesOnlyElements')) - api(files(cglibRepackJar)) api(files(javapoetRepackJar)) api(files(objenesisRepackJar)) api(project(":spring-jcl")) @@ -126,21 +96,6 @@ jar { preserveFileTimestamps = false // maybe not necessary here, but good for reproducibility manifest.attributes["Dependencies"] = "jdk.unsupported" // for WildFly (-> Objenesis 3.2) - // Inline repackaged cglib classes directly into spring-core jar - dependsOn cglibRepackJar - from(zipTree(cglibRepackJar.archivePath)) { - include "org/springframework/cglib/**" - exclude "org/springframework/cglib/beans/**" - exclude "org/springframework/cglib/core/AbstractClassGenerator*.class" - exclude "org/springframework/cglib/core/AsmApi*.class" - exclude "org/springframework/cglib/core/KeyFactory.class" - exclude "org/springframework/cglib/core/KeyFactory\$*.class" - exclude "org/springframework/cglib/core/MethodWrapper*.class" - exclude "org/springframework/cglib/core/ReflectUtils*.class" - exclude "org/springframework/cglib/proxy/Enhancer*.class" - exclude "org/springframework/cglib/proxy/MethodProxy*.class" - } - dependsOn javapoetRepackJar from(zipTree(javapoetRepackJar.archivePath)) { include "org/springframework/javapoet/**" @@ -167,16 +122,14 @@ test { } sourcesJar { - dependsOn cglibSourceJar dependsOn javapoetSourceJar dependsOn objenesisSourceJar - from cglibSource from javapoetSource from objenesisSource } eclipse { - synchronizationTasks cglibSourceJar, javapoetSourceJar, objenesisSourceJar + synchronizationTasks javapoetSourceJar, objenesisSourceJar classpath { file { whenMerged { diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Block.java b/spring-core/src/main/java/org/springframework/cglib/core/Block.java new file mode 100644 index 0000000000..f9837a12eb --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Block.java @@ -0,0 +1,49 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Label; + +public class Block +{ + private CodeEmitter e; + private Label start; + private Label end; + + public Block(CodeEmitter e) { + this.e = e; + start = e.mark(); + } + + public CodeEmitter getCodeEmitter() { + return e; + } + + public void end() { + if (end != null) { + throw new IllegalStateException("end of label already set"); + } + end = e.mark(); + } + + public Label getStart() { + return start; + } + + public Label getEnd() { + return end; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java new file mode 100644 index 0000000000..10c8997bec --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassEmitter.java @@ -0,0 +1,282 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.cglib.transform.ClassTransformer; + +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Type; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ClassEmitter extends ClassTransformer { + private ClassInfo classInfo; + private Map fieldInfo; + + private static int hookCounter; + private MethodVisitor rawStaticInit; + private CodeEmitter staticInit; + private CodeEmitter staticHook; + private Signature staticHookSig; + + public ClassEmitter(ClassVisitor cv) { + setTarget(cv); + } + + public ClassEmitter() { + super(Constants.ASM_API); + } + + public void setTarget(ClassVisitor cv) { + this.cv = cv; + fieldInfo = new HashMap(); + + // just to be safe + staticInit = staticHook = null; + staticHookSig = null; + } + + synchronized private static int getNextHook() { + return ++hookCounter; + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + public void begin_class(int version, final int access, String className, final Type superType, final Type[] interfaces, String source) { + final Type classType = Type.getType("L" + className.replace('.', '/') + ";"); + classInfo = new ClassInfo() { + public Type getType() { + return classType; + } + public Type getSuperType() { + return (superType != null) ? superType : Constants.TYPE_OBJECT; + } + public Type[] getInterfaces() { + return interfaces; + } + public int getModifiers() { + return access; + } + }; + cv.visit(version, + access, + classInfo.getType().getInternalName(), + null, + classInfo.getSuperType().getInternalName(), + TypeUtils.toInternalNames(interfaces)); + if (source != null) + cv.visitSource(source, null); + init(); + } + + public CodeEmitter getStaticHook() { + if (TypeUtils.isInterface(getAccess())) { + throw new IllegalStateException("static hook is invalid for this class"); + } + if (staticHook == null) { + staticHookSig = new Signature("CGLIB$STATICHOOK" + getNextHook(), "()V"); + staticHook = begin_method(Constants.ACC_STATIC, + staticHookSig, + null); + if (staticInit != null) { + staticInit.invoke_static_this(staticHookSig); + } + } + return staticHook; + } + + protected void init() { + } + + public int getAccess() { + return classInfo.getModifiers(); + } + + public Type getClassType() { + return classInfo.getType(); + } + + public Type getSuperType() { + return classInfo.getSuperType(); + } + + public void end_class() { + if (staticHook != null && staticInit == null) { + // force creation of static init + begin_static(); + } + if (staticInit != null) { + staticHook.return_value(); + staticHook.end_method(); + rawStaticInit.visitInsn(Constants.RETURN); + rawStaticInit.visitMaxs(0, 0); + staticInit = staticHook = null; + staticHookSig = null; + } + cv.visitEnd(); + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + if (classInfo == null) + throw new IllegalStateException("classInfo is null! " + this); + MethodVisitor v = cv.visitMethod(access, + sig.getName(), + sig.getDescriptor(), + null, + TypeUtils.toInternalNames(exceptions)); + if (sig.equals(Constants.SIG_STATIC) && !TypeUtils.isInterface(getAccess())) { + rawStaticInit = v; + MethodVisitor wrapped = new MethodVisitor(Constants.ASM_API, v) { + public void visitMaxs(int maxStack, int maxLocals) { + // ignore + } + public void visitInsn(int insn) { + if (insn != Constants.RETURN) { + super.visitInsn(insn); + } + } + }; + staticInit = new CodeEmitter(this, wrapped, access, sig, exceptions); + if (staticHook == null) { + // force static hook creation + getStaticHook(); + } else { + staticInit.invoke_static_this(staticHookSig); + } + return staticInit; + } else if (sig.equals(staticHookSig)) { + return new CodeEmitter(this, v, access, sig, exceptions) { + public boolean isStaticHook() { + return true; + } + }; + } else { + return new CodeEmitter(this, v, access, sig, exceptions); + } + } + + public CodeEmitter begin_static() { + return begin_method(Constants.ACC_STATIC, Constants.SIG_STATIC, null); + } + + public void declare_field(int access, String name, Type type, Object value) { + FieldInfo existing = (FieldInfo)fieldInfo.get(name); + FieldInfo info = new FieldInfo(access, name, type, value); + if (existing != null) { + if (!info.equals(existing)) { + throw new IllegalArgumentException("Field \"" + name + "\" has been declared differently"); + } + } else { + fieldInfo.put(name, info); + cv.visitField(access, name, type.getDescriptor(), null, value); + } + } + + // TODO: make public? + boolean isFieldDeclared(String name) { + return fieldInfo.get(name) != null; + } + + FieldInfo getFieldInfo(String name) { + FieldInfo field = (FieldInfo)fieldInfo.get(name); + if (field == null) { + throw new IllegalArgumentException("Field " + name + " is not declared in " + getClassType().getClassName()); + } + return field; + } + + static class FieldInfo { + int access; + String name; + Type type; + Object value; + + public FieldInfo(int access, String name, Type type, Object value) { + this.access = access; + this.name = name; + this.type = type; + this.value = value; + } + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof FieldInfo)) + return false; + FieldInfo other = (FieldInfo)o; + if (access != other.access || + !name.equals(other.name) || + !type.equals(other.type)) { + return false; + } + if ((value == null) ^ (other.value == null)) + return false; + if (value != null && !value.equals(other.value)) + return false; + return true; + } + + public int hashCode() { + return access ^ name.hashCode() ^ type.hashCode() ^ ((value == null) ? 0 : value.hashCode()); + } + } + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + begin_class(version, + access, + name.replace('/', '.'), + TypeUtils.fromInternalName(superName), + TypeUtils.fromInternalNames(interfaces), + null); // TODO + } + + public void visitEnd() { + end_class(); + } + + public FieldVisitor visitField(int access, + String name, + String desc, + String signature, + Object value) { + declare_field(access, name, Type.getType(desc), value); + return null; // TODO + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return begin_method(access, + new Signature(name, desc), + TypeUtils.fromInternalNames(exceptions)); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassGenerator.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassGenerator.java new file mode 100644 index 0000000000..7a52a4b10c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassGenerator.java @@ -0,0 +1,22 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.ClassVisitor; + +public interface ClassGenerator { + void generateClass(ClassVisitor v) throws Exception; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java new file mode 100644 index 0000000000..722d8199fc --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; + +abstract public class ClassInfo { + + protected ClassInfo() { + } + + abstract public Type getType(); + abstract public Type getSuperType(); + abstract public Type[] getInterfaces(); + abstract public int getModifiers(); + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof ClassInfo)) + return false; + return getType().equals(((ClassInfo)o).getType()); + } + + public int hashCode() { + return getType().hashCode(); + } + + public String toString() { + // TODO: include modifiers, superType, interfaces + return getType().getClassName(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java new file mode 100644 index 0000000000..e930032cf3 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassNameReader.java @@ -0,0 +1,65 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; + +import java.util.*; + +// TODO: optimize (ClassReader buffers entire class before accept) +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ClassNameReader { + private ClassNameReader() { + } + + private static final EarlyExitException EARLY_EXIT = new EarlyExitException(); + + @SuppressWarnings("serial") + private static class EarlyExitException extends RuntimeException { } + + public static String getClassName(ClassReader r) { + + return getClassInfo(r)[0]; + + } + + public static String[] getClassInfo(ClassReader r) { + final List array = new ArrayList(); + try { + r.accept(new ClassVisitor(Constants.ASM_API, null) { + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + array.add( name.replace('/', '.') ); + if(superName != null){ + array.add( superName.replace('/', '.') ); + } + for(int i = 0; i < interfaces.length; i++ ){ + array.add( interfaces[i].replace('/', '.') ); + } + + throw EARLY_EXIT; + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } catch (EarlyExitException e) { } + + return (String[])array.toArray( new String[]{} ); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java b/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java new file mode 100644 index 0000000000..64f7239ece --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ClassesKey.java @@ -0,0 +1,46 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +public class ClassesKey { + private static final Key FACTORY = (Key)KeyFactory.create(Key.class); + + interface Key { + Object newInstance(Object[] array); + } + + private ClassesKey() { + } + + public static Object create(Object[] array) { + return FACTORY.newInstance(classNames(array)); + } + + private static String[] classNames(Object[] objects) { + if (objects == null) { + return null; + } + String[] classNames = new String[objects.length]; + for (int i = 0; i < objects.length; i++) { + Object object = objects[i]; + if (object != null) { + Class aClass = object.getClass(); + classNames[i] = aClass == null ? null : aClass.getName(); + } + } + return classNames; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java b/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java new file mode 100644 index 0000000000..a683765f9d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/CodeEmitter.java @@ -0,0 +1,870 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.io.*; +import java.util.*; +import org.springframework.asm.*; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +@SuppressWarnings("fallthrough") +public class CodeEmitter extends LocalVariablesSorter { + private static final Signature BOOLEAN_VALUE = + TypeUtils.parseSignature("boolean booleanValue()"); + private static final Signature CHAR_VALUE = + TypeUtils.parseSignature("char charValue()"); + private static final Signature LONG_VALUE = + TypeUtils.parseSignature("long longValue()"); + private static final Signature DOUBLE_VALUE = + TypeUtils.parseSignature("double doubleValue()"); + private static final Signature FLOAT_VALUE = + TypeUtils.parseSignature("float floatValue()"); + private static final Signature INT_VALUE = + TypeUtils.parseSignature("int intValue()"); + private static final Signature CSTRUCT_NULL = + TypeUtils.parseConstructor(""); + private static final Signature CSTRUCT_STRING = + TypeUtils.parseConstructor("String"); + + public static final int ADD = Constants.IADD; + public static final int MUL = Constants.IMUL; + public static final int XOR = Constants.IXOR; + public static final int USHR = Constants.IUSHR; + public static final int SUB = Constants.ISUB; + public static final int DIV = Constants.IDIV; + public static final int NEG = Constants.INEG; + public static final int REM = Constants.IREM; + public static final int AND = Constants.IAND; + public static final int OR = Constants.IOR; + + public static final int GT = Constants.IFGT; + public static final int LT = Constants.IFLT; + public static final int GE = Constants.IFGE; + public static final int LE = Constants.IFLE; + public static final int NE = Constants.IFNE; + public static final int EQ = Constants.IFEQ; + + private ClassEmitter ce; + private State state; + + private static class State + extends MethodInfo + { + ClassInfo classInfo; + int access; + Signature sig; + Type[] argumentTypes; + int localOffset; + Type[] exceptionTypes; + + State(ClassInfo classInfo, int access, Signature sig, Type[] exceptionTypes) { + this.classInfo = classInfo; + this.access = access; + this.sig = sig; + this.exceptionTypes = exceptionTypes; + localOffset = TypeUtils.isStatic(access) ? 0 : 1; + argumentTypes = sig.getArgumentTypes(); + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + public int getModifiers() { + return access; + } + + public Signature getSignature() { + return sig; + } + + public Type[] getExceptionTypes() { + return exceptionTypes; + } + + public Attribute getAttribute() { + // TODO + return null; + } + } + + CodeEmitter(ClassEmitter ce, MethodVisitor mv, int access, Signature sig, Type[] exceptionTypes) { + super(access, sig.getDescriptor(), mv); + this.ce = ce; + state = new State(ce.getClassInfo(), access, sig, exceptionTypes); + } + + public CodeEmitter(CodeEmitter wrap) { + super(wrap); + this.ce = wrap.ce; + this.state = wrap.state; + } + + public boolean isStaticHook() { + return false; + } + + public Signature getSignature() { + return state.sig; + } + + public Type getReturnType() { + return state.sig.getReturnType(); + } + + public MethodInfo getMethodInfo() { + return state; + } + + public ClassEmitter getClassEmitter() { + return ce; + } + + public void end_method() { + visitMaxs(0, 0); + } + + public Block begin_block() { + return new Block(this); + } + + public void catch_exception(Block block, Type exception) { + if (block.getEnd() == null) { + throw new IllegalStateException("end of block is unset"); + } + mv.visitTryCatchBlock(block.getStart(), + block.getEnd(), + mark(), + exception.getInternalName()); + } + + public void goTo(Label label) { mv.visitJumpInsn(Constants.GOTO, label); } + public void ifnull(Label label) { mv.visitJumpInsn(Constants.IFNULL, label); } + public void ifnonnull(Label label) { mv.visitJumpInsn(Constants.IFNONNULL, label); } + + public void if_jump(int mode, Label label) { + mv.visitJumpInsn(mode, label); + } + + public void if_icmp(int mode, Label label) { + if_cmp(Type.INT_TYPE, mode, label); + } + + public void if_cmp(Type type, int mode, Label label) { + int intOp = -1; + int jumpmode = mode; + switch (mode) { + case GE: jumpmode = LT; break; + case LE: jumpmode = GT; break; + } + switch (type.getSort()) { + case Type.LONG: + mv.visitInsn(Constants.LCMP); + break; + case Type.DOUBLE: + mv.visitInsn(Constants.DCMPG); + break; + case Type.FLOAT: + mv.visitInsn(Constants.FCMPG); + break; + case Type.ARRAY: + case Type.OBJECT: + switch (mode) { + case EQ: + mv.visitJumpInsn(Constants.IF_ACMPEQ, label); + return; + case NE: + mv.visitJumpInsn(Constants.IF_ACMPNE, label); + return; + } + throw new IllegalArgumentException("Bad comparison for type " + type); + default: + switch (mode) { + case EQ: intOp = Constants.IF_ICMPEQ; break; + case NE: intOp = Constants.IF_ICMPNE; break; + case GE: swap(); /* fall through */ + case LT: intOp = Constants.IF_ICMPLT; break; + case LE: swap(); /* fall through */ + case GT: intOp = Constants.IF_ICMPGT; break; + } + mv.visitJumpInsn(intOp, label); + return; + } + if_jump(jumpmode, label); + } + + public void pop() { mv.visitInsn(Constants.POP); } + public void pop2() { mv.visitInsn(Constants.POP2); } + public void dup() { mv.visitInsn(Constants.DUP); } + public void dup2() { mv.visitInsn(Constants.DUP2); } + public void dup_x1() { mv.visitInsn(Constants.DUP_X1); } + public void dup_x2() { mv.visitInsn(Constants.DUP_X2); } + public void dup2_x1() { mv.visitInsn(Constants.DUP2_X1); } + public void dup2_x2() { mv.visitInsn(Constants.DUP2_X2); } + public void swap() { mv.visitInsn(Constants.SWAP); } + public void aconst_null() { mv.visitInsn(Constants.ACONST_NULL); } + + public void swap(Type prev, Type type) { + if (type.getSize() == 1) { + if (prev.getSize() == 1) { + swap(); // same as dup_x1(), pop(); + } else { + dup_x2(); + pop(); + } + } else { + if (prev.getSize() == 1) { + dup2_x1(); + pop2(); + } else { + dup2_x2(); + pop2(); + } + } + } + + public void monitorenter() { mv.visitInsn(Constants.MONITORENTER); } + public void monitorexit() { mv.visitInsn(Constants.MONITOREXIT); } + + public void math(int op, Type type) { mv.visitInsn(type.getOpcode(op)); } + + public void array_load(Type type) { mv.visitInsn(type.getOpcode(Constants.IALOAD)); } + public void array_store(Type type) { mv.visitInsn(type.getOpcode(Constants.IASTORE)); } + + /** + * Casts from one primitive numeric type to another + */ + public void cast_numeric(Type from, Type to) { + if (from != to) { + if (from == Type.DOUBLE_TYPE) { + if (to == Type.FLOAT_TYPE) { + mv.visitInsn(Constants.D2F); + } else if (to == Type.LONG_TYPE) { + mv.visitInsn(Constants.D2L); + } else { + mv.visitInsn(Constants.D2I); + cast_numeric(Type.INT_TYPE, to); + } + } else if (from == Type.FLOAT_TYPE) { + if (to == Type.DOUBLE_TYPE) { + mv.visitInsn(Constants.F2D); + } else if (to == Type.LONG_TYPE) { + mv.visitInsn(Constants.F2L); + } else { + mv.visitInsn(Constants.F2I); + cast_numeric(Type.INT_TYPE, to); + } + } else if (from == Type.LONG_TYPE) { + if (to == Type.DOUBLE_TYPE) { + mv.visitInsn(Constants.L2D); + } else if (to == Type.FLOAT_TYPE) { + mv.visitInsn(Constants.L2F); + } else { + mv.visitInsn(Constants.L2I); + cast_numeric(Type.INT_TYPE, to); + } + } else { + if (to == Type.BYTE_TYPE) { + mv.visitInsn(Constants.I2B); + } else if (to == Type.CHAR_TYPE) { + mv.visitInsn(Constants.I2C); + } else if (to == Type.DOUBLE_TYPE) { + mv.visitInsn(Constants.I2D); + } else if (to == Type.FLOAT_TYPE) { + mv.visitInsn(Constants.I2F); + } else if (to == Type.LONG_TYPE) { + mv.visitInsn(Constants.I2L); + } else if (to == Type.SHORT_TYPE) { + mv.visitInsn(Constants.I2S); + } + } + } + } + + public void push(int i) { + if (i < -1) { + mv.visitLdcInsn(i); + } else if (i <= 5) { + mv.visitInsn(TypeUtils.ICONST(i)); + } else if (i <= Byte.MAX_VALUE) { + mv.visitIntInsn(Constants.BIPUSH, i); + } else if (i <= Short.MAX_VALUE) { + mv.visitIntInsn(Constants.SIPUSH, i); + } else { + mv.visitLdcInsn(i); + } + } + + public void push(long value) { + if (value == 0L || value == 1L) { + mv.visitInsn(TypeUtils.LCONST(value)); + } else { + mv.visitLdcInsn(value); + } + } + + public void push(float value) { + if (value == 0f || value == 1f || value == 2f) { + mv.visitInsn(TypeUtils.FCONST(value)); + } else { + mv.visitLdcInsn(value); + } + } + public void push(double value) { + if (value == 0d || value == 1d) { + mv.visitInsn(TypeUtils.DCONST(value)); + } else { + mv.visitLdcInsn(value); + } + } + + public void push(String value) { + mv.visitLdcInsn(value); + } + + public void newarray() { + newarray(Constants.TYPE_OBJECT); + } + + public void newarray(Type type) { + if (TypeUtils.isPrimitive(type)) { + mv.visitIntInsn(Constants.NEWARRAY, TypeUtils.NEWARRAY(type)); + } else { + emit_type(Constants.ANEWARRAY, type); + } + } + + public void arraylength() { + mv.visitInsn(Constants.ARRAYLENGTH); + } + + public void load_this() { + if (TypeUtils.isStatic(state.access)) { + throw new IllegalStateException("no 'this' pointer within static method"); + } + mv.visitVarInsn(Constants.ALOAD, 0); + } + + /** + * Pushes all of the arguments of the current method onto the stack. + */ + public void load_args() { + load_args(0, state.argumentTypes.length); + } + + /** + * Pushes the specified argument of the current method onto the stack. + * @param index the zero-based index into the argument list + */ + public void load_arg(int index) { + load_local(state.argumentTypes[index], + state.localOffset + skipArgs(index)); + } + + // zero-based (see load_this) + public void load_args(int fromArg, int count) { + int pos = state.localOffset + skipArgs(fromArg); + for (int i = 0; i < count; i++) { + Type t = state.argumentTypes[fromArg + i]; + load_local(t, pos); + pos += t.getSize(); + } + } + + private int skipArgs(int numArgs) { + int amount = 0; + for (int i = 0; i < numArgs; i++) { + amount += state.argumentTypes[i].getSize(); + } + return amount; + } + + private void load_local(Type t, int pos) { + // TODO: make t == null ok? + mv.visitVarInsn(t.getOpcode(Constants.ILOAD), pos); + } + + private void store_local(Type t, int pos) { + // TODO: make t == null ok? + mv.visitVarInsn(t.getOpcode(Constants.ISTORE), pos); + } + + public void iinc(Local local, int amount) { + mv.visitIincInsn(local.getIndex(), amount); + } + + public void store_local(Local local) { + store_local(local.getType(), local.getIndex()); + } + + public void load_local(Local local) { + load_local(local.getType(), local.getIndex()); + } + + public void return_value() { + mv.visitInsn(state.sig.getReturnType().getOpcode(Constants.IRETURN)); + } + + public void getfield(String name) { + ClassEmitter.FieldInfo info = ce.getFieldInfo(name); + int opcode = TypeUtils.isStatic(info.access) ? Constants.GETSTATIC : Constants.GETFIELD; + emit_field(opcode, ce.getClassType(), name, info.type); + } + + public void putfield(String name) { + ClassEmitter.FieldInfo info = ce.getFieldInfo(name); + int opcode = TypeUtils.isStatic(info.access) ? Constants.PUTSTATIC : Constants.PUTFIELD; + emit_field(opcode, ce.getClassType(), name, info.type); + } + + public void super_getfield(String name, Type type) { + emit_field(Constants.GETFIELD, ce.getSuperType(), name, type); + } + + public void super_putfield(String name, Type type) { + emit_field(Constants.PUTFIELD, ce.getSuperType(), name, type); + } + + public void super_getstatic(String name, Type type) { + emit_field(Constants.GETSTATIC, ce.getSuperType(), name, type); + } + + public void super_putstatic(String name, Type type) { + emit_field(Constants.PUTSTATIC, ce.getSuperType(), name, type); + } + + public void getfield(Type owner, String name, Type type) { + emit_field(Constants.GETFIELD, owner, name, type); + } + + public void putfield(Type owner, String name, Type type) { + emit_field(Constants.PUTFIELD, owner, name, type); + } + + public void getstatic(Type owner, String name, Type type) { + emit_field(Constants.GETSTATIC, owner, name, type); + } + + public void putstatic(Type owner, String name, Type type) { + emit_field(Constants.PUTSTATIC, owner, name, type); + } + + // package-protected for EmitUtils, try to fix + void emit_field(int opcode, Type ctype, String name, Type ftype) { + mv.visitFieldInsn(opcode, + ctype.getInternalName(), + name, + ftype.getDescriptor()); + } + + public void super_invoke() { + super_invoke(state.sig); + } + + public void super_invoke(Signature sig) { + emit_invoke(Constants.INVOKESPECIAL, ce.getSuperType(), sig, false); + } + + public void invoke_constructor(Type type) { + invoke_constructor(type, CSTRUCT_NULL); + } + + public void super_invoke_constructor() { + invoke_constructor(ce.getSuperType()); + } + + public void invoke_constructor_this() { + invoke_constructor(ce.getClassType()); + } + + private void emit_invoke(int opcode, Type type, Signature sig, boolean isInterface) { + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME) && + ((opcode == Constants.INVOKEVIRTUAL) || + (opcode == Constants.INVOKESTATIC))) { + // TODO: error + } + mv.visitMethodInsn(opcode, + type.getInternalName(), + sig.getName(), + sig.getDescriptor(), + isInterface); + } + + public void invoke_interface(Type owner, Signature sig) { + emit_invoke(Constants.INVOKEINTERFACE, owner, sig, true); + } + + public void invoke_virtual(Type owner, Signature sig) { + emit_invoke(Constants.INVOKEVIRTUAL, owner, sig, false); + } + + @Deprecated + public void invoke_static(Type owner, Signature sig) { + invoke_static(owner, sig, false); + } + + public void invoke_static(Type owner, Signature sig, boolean isInterface) { + emit_invoke(Constants.INVOKESTATIC, owner, sig, isInterface); + } + + public void invoke_virtual_this(Signature sig) { + invoke_virtual(ce.getClassType(), sig); + } + + public void invoke_static_this(Signature sig) { + invoke_static(ce.getClassType(), sig); + } + + public void invoke_constructor(Type type, Signature sig) { + emit_invoke(Constants.INVOKESPECIAL, type, sig, false); + } + + public void invoke_constructor_this(Signature sig) { + invoke_constructor(ce.getClassType(), sig); + } + + public void super_invoke_constructor(Signature sig) { + invoke_constructor(ce.getSuperType(), sig); + } + + public void new_instance_this() { + new_instance(ce.getClassType()); + } + + public void new_instance(Type type) { + emit_type(Constants.NEW, type); + } + + private void emit_type(int opcode, Type type) { + String desc; + if (TypeUtils.isArray(type)) { + desc = type.getDescriptor(); + } else { + desc = type.getInternalName(); + } + mv.visitTypeInsn(opcode, desc); + } + + public void aaload(int index) { + push(index); + aaload(); + } + + public void aaload() { mv.visitInsn(Constants.AALOAD); } + public void aastore() { mv.visitInsn(Constants.AASTORE); } + public void athrow() { mv.visitInsn(Constants.ATHROW); } + + public Label make_label() { + return new Label(); + } + + public Local make_local() { + return make_local(Constants.TYPE_OBJECT); + } + + public Local make_local(Type type) { + return new Local(newLocal(type.getSize()), type); + } + + public void checkcast_this() { + checkcast(ce.getClassType()); + } + + public void checkcast(Type type) { + if (!type.equals(Constants.TYPE_OBJECT)) { + emit_type(Constants.CHECKCAST, type); + } + } + + public void instance_of(Type type) { + emit_type(Constants.INSTANCEOF, type); + } + + public void instance_of_this() { + instance_of(ce.getClassType()); + } + + public void process_switch(int[] keys, ProcessSwitchCallback callback) { + float density; + if (keys.length == 0) { + density = 0; + } else { + density = (float)keys.length / (keys[keys.length - 1] - keys[0] + 1); + } + process_switch(keys, callback, density >= 0.5f); + } + + public void process_switch(int[] keys, ProcessSwitchCallback callback, boolean useTable) { + if (!isSorted(keys)) + throw new IllegalArgumentException("keys to switch must be sorted ascending"); + Label def = make_label(); + Label end = make_label(); + + try { + if (keys.length > 0) { + int len = keys.length; + int min = keys[0]; + int max = keys[len - 1]; + int range = max - min + 1; + + if (useTable) { + Label[] labels = new Label[range]; + Arrays.fill(labels, def); + for (int i = 0; i < len; i++) { + labels[keys[i] - min] = make_label(); + } + mv.visitTableSwitchInsn(min, max, def, labels); + for (int i = 0; i < range; i++) { + Label label = labels[i]; + if (label != def) { + mark(label); + callback.processCase(i + min, end); + } + } + } else { + Label[] labels = new Label[len]; + for (int i = 0; i < len; i++) { + labels[i] = make_label(); + } + mv.visitLookupSwitchInsn(def, keys, labels); + for (int i = 0; i < len; i++) { + mark(labels[i]); + callback.processCase(keys[i], end); + } + } + } + + mark(def); + callback.processDefault(); + mark(end); + + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + + private static boolean isSorted(int[] keys) { + for (int i = 1; i < keys.length; i++) { + if (keys[i] < keys[i - 1]) + return false; + } + return true; + } + + public void mark(Label label) { + mv.visitLabel(label); + } + + Label mark() { + Label label = make_label(); + mv.visitLabel(label); + return label; + } + + public void push(boolean value) { + push(value ? 1 : 0); + } + + /** + * Toggles the integer on the top of the stack from 1 to 0 or vice versa + */ + public void not() { + push(1); + math(XOR, Type.INT_TYPE); + } + + public void throw_exception(Type type, String msg) { + new_instance(type); + dup(); + push(msg); + invoke_constructor(type, CSTRUCT_STRING); + athrow(); + } + + /** + * If the argument is a primitive class, replaces the primitive value + * on the top of the stack with the wrapped (Object) equivalent. For + * example, char -> Character. + * If the class is Void, a null is pushed onto the stack instead. + * @param type the class indicating the current type of the top stack value + */ + public void box(Type type) { + if (TypeUtils.isPrimitive(type)) { + if (type == Type.VOID_TYPE) { + aconst_null(); + } else { + Type boxed = TypeUtils.getBoxedType(type); + new_instance(boxed); + if (type.getSize() == 2) { + // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o + dup_x2(); + dup_x2(); + pop(); + } else { + // p -> po -> opo -> oop -> o + dup_x1(); + swap(); + } + invoke_constructor(boxed, new Signature(Constants.CONSTRUCTOR_NAME, Type.VOID_TYPE, new Type[]{ type })); + } + } + } + + /** + * If the argument is a primitive class, replaces the object + * on the top of the stack with the unwrapped (primitive) + * equivalent. For example, Character -> char. + * @param type the class indicating the desired type of the top stack value + * @return true if the value was unboxed + */ + public void unbox(Type type) { + Type t = Constants.TYPE_NUMBER; + Signature sig = null; + switch (type.getSort()) { + case Type.VOID: + return; + case Type.CHAR: + t = Constants.TYPE_CHARACTER; + sig = CHAR_VALUE; + break; + case Type.BOOLEAN: + t = Constants.TYPE_BOOLEAN; + sig = BOOLEAN_VALUE; + break; + case Type.DOUBLE: + sig = DOUBLE_VALUE; + break; + case Type.FLOAT: + sig = FLOAT_VALUE; + break; + case Type.LONG: + sig = LONG_VALUE; + break; + case Type.INT: + case Type.SHORT: + case Type.BYTE: + sig = INT_VALUE; + } + + if (sig == null) { + checkcast(type); + } else { + checkcast(t); + invoke_virtual(t, sig); + } + } + + /** + * Allocates and fills an Object[] array with the arguments to the + * current method. Primitive values are inserted as their boxed + * (Object) equivalents. + */ + public void create_arg_array() { + /* generates: + Object[] args = new Object[]{ arg1, new Integer(arg2) }; + */ + + push(state.argumentTypes.length); + newarray(); + for (int i = 0; i < state.argumentTypes.length; i++) { + dup(); + push(i); + load_arg(i); + box(state.argumentTypes[i]); + aastore(); + } + } + + + /** + * Pushes a zero onto the stack if the argument is a primitive class, or a null otherwise. + */ + public void zero_or_null(Type type) { + if (TypeUtils.isPrimitive(type)) { + switch (type.getSort()) { + case Type.DOUBLE: + push(0d); + break; + case Type.LONG: + push(0L); + break; + case Type.FLOAT: + push(0f); + break; + case Type.VOID: + aconst_null(); + default: + push(0); + } + } else { + aconst_null(); + } + } + + /** + * Unboxes the object on the top of the stack. If the object is null, the + * unboxed primitive value becomes zero. + */ + public void unbox_or_zero(Type type) { + if (TypeUtils.isPrimitive(type)) { + if (type != Type.VOID_TYPE) { + Label nonNull = make_label(); + Label end = make_label(); + dup(); + ifnonnull(nonNull); + pop(); + zero_or_null(type); + goTo(end); + mark(nonNull); + unbox(type); + mark(end); + } + } else { + checkcast(type); + } + } + + public void visitMaxs(int maxStack, int maxLocals) { + if (!TypeUtils.isAbstract(state.access)) { + mv.visitMaxs(0, 0); + } + } + + public void invoke(MethodInfo method, Type virtualType) { + ClassInfo classInfo = method.getClassInfo(); + Type type = classInfo.getType(); + Signature sig = method.getSignature(); + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) { + invoke_constructor(type, sig); + } else if (TypeUtils.isStatic(method.getModifiers())) { + invoke_static(type, sig, TypeUtils.isInterface(classInfo.getModifiers())); + } else if (TypeUtils.isInterface(classInfo.getModifiers())) { + invoke_interface(type, sig); + } else { + invoke_virtual(virtualType, sig); + } + } + + public void invoke(MethodInfo method) { + invoke(method, method.getClassInfo().getType()); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java b/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java new file mode 100644 index 0000000000..48b3495a50 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/CodeGenerationException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +/** + * @version $Id: CodeGenerationException.java,v 1.3 2004/06/24 21:15:21 herbyderby Exp $ + */ +@SuppressWarnings("serial") +public class CodeGenerationException extends RuntimeException { + private Throwable cause; + + public CodeGenerationException(Throwable cause) { + super(cause.getClass().getName() + "-->" + cause.getMessage()); + this.cause = cause; + } + + public Throwable getCause() { + return cause; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java new file mode 100644 index 0000000000..aa2f1d7fa8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/CollectionUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.util.*; +import java.lang.reflect.Array; + +/** + * @author Chris Nokleberg + * @version $Id: CollectionUtils.java,v 1.7 2004/06/24 21:15:21 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class CollectionUtils { + private CollectionUtils() { } + + public static Map bucket(Collection c, Transformer t) { + Map buckets = new HashMap(); + for (Iterator it = c.iterator(); it.hasNext();) { + Object value = it.next(); + Object key = t.transform(value); + List bucket = (List)buckets.get(key); + if (bucket == null) { + buckets.put(key, bucket = new LinkedList()); + } + bucket.add(value); + } + return buckets; + } + + public static void reverse(Map source, Map target) { + for (Iterator it = source.keySet().iterator(); it.hasNext();) { + Object key = it.next(); + target.put(source.get(key), key); + } + } + + public static Collection filter(Collection c, Predicate p) { + Iterator it = c.iterator(); + while (it.hasNext()) { + if (!p.evaluate(it.next())) { + it.remove(); + } + } + return c; + } + + public static List transform(Collection c, Transformer t) { + List result = new ArrayList(c.size()); + for (Iterator it = c.iterator(); it.hasNext();) { + result.add(t.transform(it.next())); + } + return result; + } + + public static Map getIndexMap(List list) { + Map indexes = new HashMap(); + int index = 0; + for (Iterator it = list.iterator(); it.hasNext();) { + indexes.put(it.next(), index++); + } + return indexes; + } +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Constants.java b/spring-core/src/main/java/org/springframework/cglib/core/Constants.java new file mode 100644 index 0000000000..1fa8e2e3cc --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Constants.java @@ -0,0 +1,73 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +/** + * @author Juozas Baliuka baliuka@mwm.lt + * @version $Id: Constants.java,v 1.21 2006/03/05 02:43:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public interface Constants extends org.springframework.asm.Opcodes { + + /* Indicates the ASM API version that is used throughout cglib */ + public static final int ASM_API = AsmApi.value(); + + public static final Class[] EMPTY_CLASS_ARRAY = {}; + public static final Type[] TYPES_EMPTY = {}; + + public static final Signature SIG_STATIC = + TypeUtils.parseSignature("void ()"); + + public static final Type TYPE_OBJECT_ARRAY = TypeUtils.parseType("Object[]"); + public static final Type TYPE_CLASS_ARRAY = TypeUtils.parseType("Class[]"); + public static final Type TYPE_STRING_ARRAY = TypeUtils.parseType("String[]"); + + public static final Type TYPE_OBJECT = TypeUtils.parseType("Object"); + public static final Type TYPE_CLASS = TypeUtils.parseType("Class"); + public static final Type TYPE_CLASS_LOADER = TypeUtils.parseType("ClassLoader"); + public static final Type TYPE_CHARACTER = TypeUtils.parseType("Character"); + public static final Type TYPE_BOOLEAN = TypeUtils.parseType("Boolean"); + public static final Type TYPE_DOUBLE = TypeUtils.parseType("Double"); + public static final Type TYPE_FLOAT = TypeUtils.parseType("Float"); + public static final Type TYPE_LONG = TypeUtils.parseType("Long"); + public static final Type TYPE_INTEGER = TypeUtils.parseType("Integer"); + public static final Type TYPE_SHORT = TypeUtils.parseType("Short"); + public static final Type TYPE_BYTE = TypeUtils.parseType("Byte"); + public static final Type TYPE_NUMBER = TypeUtils.parseType("Number"); + public static final Type TYPE_STRING = TypeUtils.parseType("String"); + public static final Type TYPE_THROWABLE = TypeUtils.parseType("Throwable"); + public static final Type TYPE_BIG_INTEGER = TypeUtils.parseType("java.math.BigInteger"); + public static final Type TYPE_BIG_DECIMAL = TypeUtils.parseType("java.math.BigDecimal"); + public static final Type TYPE_STRING_BUFFER = TypeUtils.parseType("StringBuffer"); + public static final Type TYPE_RUNTIME_EXCEPTION = TypeUtils.parseType("RuntimeException"); + public static final Type TYPE_ERROR = TypeUtils.parseType("Error"); + public static final Type TYPE_SYSTEM = TypeUtils.parseType("System"); + public static final Type TYPE_SIGNATURE = TypeUtils.parseType("org.springframework.cglib.core.Signature"); + public static final Type TYPE_TYPE = Type.getType(Type.class); + + public static final String CONSTRUCTOR_NAME = ""; + public static final String STATIC_NAME = ""; + public static final String SOURCE_FILE = ""; + public static final String SUID_FIELD_NAME = "serialVersionUID"; + + public static final int PRIVATE_FINAL_STATIC = ACC_PRIVATE | ACC_FINAL | ACC_STATIC; + + public static final int SWITCH_STYLE_TRIE = 0; + public static final int SWITCH_STYLE_HASH = 1; + public static final int SWITCH_STYLE_HASHONLY = 2; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Converter.java b/spring-core/src/main/java/org/springframework/cglib/core/Converter.java new file mode 100644 index 0000000000..d8a028d246 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Converter.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public interface Converter { + Object convert(Object value, Class target, Object context); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Customizer.java b/spring-core/src/main/java/org/springframework/cglib/core/Customizer.java new file mode 100644 index 0000000000..176dce2d67 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Customizer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +/** + * Customizes key types for {@link KeyFactory} when building equals, hashCode, and toString. + * For customization of field types, use {@link FieldTypeCustomizer} + * + * @see KeyFactory#CLASS_BY_NAME + */ +public interface Customizer extends KeyFactoryCustomizer { + void customize(CodeEmitter e, Type type); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DebuggingClassWriter.java b/spring-core/src/main/java/org/springframework/cglib/core/DebuggingClassWriter.java new file mode 100644 index 0000000000..e8021681e0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/DebuggingClassWriter.java @@ -0,0 +1,108 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.ClassWriter; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Opcodes; + +import java.io.*; +import java.lang.reflect.Constructor; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class DebuggingClassWriter extends ClassVisitor { + + public static final String DEBUG_LOCATION_PROPERTY = "cglib.debugLocation"; + + private static String debugLocation; + private static Constructor traceCtor; + + private String className; + private String superName; + + static { + debugLocation = System.getProperty(DEBUG_LOCATION_PROPERTY); + if (debugLocation != null) { + System.err.println("CGLIB debugging enabled, writing to '" + debugLocation + "'"); + try { + Class clazz = Class.forName("org.springframework.asm.util.TraceClassVisitor"); + traceCtor = clazz.getConstructor(new Class[]{ClassVisitor.class, PrintWriter.class}); + } catch (Throwable ignore) { + } + } + } + + public DebuggingClassWriter(int flags) { + super(Constants.ASM_API, new ClassWriter(flags)); + } + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + className = name.replace('/', '.'); + this.superName = superName.replace('/', '.'); + super.visit(version, access, name, signature, superName, interfaces); + } + + public String getClassName() { + return className; + } + + public String getSuperName() { + return superName; + } + + public byte[] toByteArray() { + + byte[] b = ((ClassWriter) DebuggingClassWriter.super.cv).toByteArray(); + if (debugLocation != null) { + String dirs = className.replace('.', File.separatorChar); + try { + new File(debugLocation + File.separatorChar + dirs).getParentFile().mkdirs(); + + File file = new File(new File(debugLocation), dirs + ".class"); + OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + try { + out.write(b); + } finally { + out.close(); + } + + if (traceCtor != null) { + file = new File(new File(debugLocation), dirs + ".asm"); + out = new BufferedOutputStream(new FileOutputStream(file)); + try { + ClassReader cr = new ClassReader(b); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out)); + ClassVisitor tcv = (ClassVisitor)traceCtor.newInstance(new Object[]{null, pw}); + cr.accept(tcv, 0); + pw.flush(); + } finally { + out.close(); + } + } + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + return b; + } + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java b/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java new file mode 100644 index 0000000000..27607cf74b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/DefaultGeneratorStrategy.java @@ -0,0 +1,47 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.ClassWriter; + +public class DefaultGeneratorStrategy implements GeneratorStrategy { + public static final DefaultGeneratorStrategy INSTANCE = new DefaultGeneratorStrategy(); + + public byte[] generate(ClassGenerator cg) throws Exception { + DebuggingClassWriter cw = getClassVisitor(); + transform(cg).generateClass(cw); + return transform(cw.toByteArray()); + } + + protected DebuggingClassWriter getClassVisitor() throws Exception { + return new DebuggingClassWriter(ClassWriter.COMPUTE_FRAMES); + } + + protected final ClassWriter getClassWriter() { + // Cause compile / runtime errors for people who implemented the old + // interface without using @Override + throw new UnsupportedOperationException("You are calling " + + "getClassWriter, which no longer exists in this cglib version."); + } + + protected byte[] transform(byte[] b) throws Exception { + return b; + } + + protected ClassGenerator transform(ClassGenerator cg) throws Exception { + return cg; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java b/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java new file mode 100644 index 0000000000..e224da19d3 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/DefaultNamingPolicy.java @@ -0,0 +1,71 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.util.Set; + +/** + * The default policy used by {@link AbstractClassGenerator}. + * Generates names such as + *

org.springframework.cglib.Foo$$EnhancerByCGLIB$$38272841

+ * This is composed of a prefix based on the name of the superclass, a fixed + * string incorporating the CGLIB class responsible for generation, and a + * hashcode derived from the parameters used to create the object. If the same + * name has been previously been used in the same ClassLoader, a + * suffix is added to ensure uniqueness. + */ +public class DefaultNamingPolicy implements NamingPolicy { + public static final DefaultNamingPolicy INSTANCE = new DefaultNamingPolicy(); + + /** + * This allows to test collisions of {@code key.hashCode()}. + */ + private final static boolean STRESS_HASH_CODE = Boolean.getBoolean("org.springframework.cglib.test.stressHashCodes"); + + public String getClassName(String prefix, String source, Object key, Predicate names) { + if (prefix == null) { + prefix = "org.springframework.cglib.empty.Object"; + } else if (prefix.startsWith("java")) { + prefix = "$" + prefix; + } + String base = + prefix + "$$" + + source.substring(source.lastIndexOf('.') + 1) + + getTag() + "$$" + + Integer.toHexString(STRESS_HASH_CODE ? 0 : key.hashCode()); + String attempt = base; + int index = 2; + while (names.evaluate(attempt)) + attempt = base + "_" + index++; + return attempt; + } + + /** + * Returns a string which is incorporated into every generated class name. + * By default returns "ByCGLIB" + */ + protected String getTag() { + return "ByCGLIB"; + } + + public int hashCode() { + return getTag().hashCode(); + } + + public boolean equals(Object o) { + return (o instanceof DefaultNamingPolicy) && ((DefaultNamingPolicy) o).getTag().equals(getTag()); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java b/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java new file mode 100644 index 0000000000..a12cf1ce62 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/DuplicatesPredicate.java @@ -0,0 +1,165 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class DuplicatesPredicate implements Predicate { + private final Set unique; + private final Set rejected; + + /** + * Constructs a DuplicatesPredicate that will allow subclass bridge methods to be preferred over + * superclass non-bridge methods. + */ + public DuplicatesPredicate() { + unique = new HashSet(); + rejected = Collections.emptySet(); + } + + /** + * Constructs a DuplicatesPredicate that prefers using superclass non-bridge methods despite a + * subclass method with the same signtaure existing (if the subclass is a bridge method). + */ + public DuplicatesPredicate(List allMethods) { + rejected = new HashSet(); + unique = new HashSet(); + + // Traverse through the methods and capture ones that are bridge + // methods when a subsequent method (from a non-interface superclass) + // has the same signature but isn't a bridge. Record these so that + // we avoid using them when filtering duplicates. + Map scanned = new HashMap(); + Map suspects = new HashMap(); + for (Object o : allMethods) { + Method method = (Method) o; + Object sig = MethodWrapper.create(method); + Method existing = (Method) scanned.get(sig); + if (existing == null) { + scanned.put(sig, method); + } else if (!suspects.containsKey(sig) && existing.isBridge() && !method.isBridge()) { + // TODO: this currently only will capture a single bridge. it will not work + // if there's Child.bridge1 Middle.bridge2 Parent.concrete. (we'd offer the 2nd bridge). + // no idea if that's even possible tho... + suspects.put(sig, existing); + } + } + + if (!suspects.isEmpty()) { + Set classes = new HashSet(); + UnnecessaryBridgeFinder finder = new UnnecessaryBridgeFinder(rejected); + for (Object o : suspects.values()) { + Method m = (Method) o; + classes.add(m.getDeclaringClass()); + finder.addSuspectMethod(m); + } + for (Object o : classes) { + Class c = (Class) o; + try { + ClassLoader cl = getClassLoader(c); + if (cl == null) { + continue; + } + InputStream is = cl.getResourceAsStream(c.getName().replace('.', '/') + ".class"); + if (is == null) { + continue; + } + try { + new ClassReader(is).accept(finder, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + } finally { + is.close(); + } + } catch (IOException ignored) { + } + } + } + } + + public boolean evaluate(Object arg) { + return !rejected.contains(arg) && unique.add(MethodWrapper.create((Method) arg)); + } + + private static ClassLoader getClassLoader(Class c) { + ClassLoader cl = c.getClassLoader(); + if (cl == null) { + cl = DuplicatesPredicate.class.getClassLoader(); + } + if (cl == null) { + cl = Thread.currentThread().getContextClassLoader(); + } + return cl; + } + + private static class UnnecessaryBridgeFinder extends ClassVisitor { + private final Set rejected; + + private Signature currentMethodSig = null; + private Map methods = new HashMap(); + + UnnecessaryBridgeFinder(Set rejected) { + super(Constants.ASM_API); + this.rejected = rejected; + } + + void addSuspectMethod(Method m) { + methods.put(ReflectUtils.getSignature(m), m); + } + + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) {} + + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + Signature sig = new Signature(name, desc); + final Method currentMethod = (Method) methods.remove(sig); + if (currentMethod != null) { + currentMethodSig = sig; + return new MethodVisitor(Constants.ASM_API) { + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESPECIAL && currentMethodSig != null) { + Signature target = new Signature(name, desc); + if (target.equals(currentMethodSig)) { + rejected.add(currentMethod); + } + currentMethodSig = null; + } + } + }; + } else { + return null; + } + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java new file mode 100644 index 0000000000..e5b59497df --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/EmitUtils.java @@ -0,0 +1,961 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; + +import org.springframework.cglib.core.internal.CustomizerRegistry; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked", "static", "fallthrough", "deprecation"}) +public class EmitUtils { + private static final Signature CSTRUCT_NULL = + TypeUtils.parseConstructor(""); + private static final Signature CSTRUCT_THROWABLE = + TypeUtils.parseConstructor("Throwable"); + + private static final Signature GET_NAME = + TypeUtils.parseSignature("String getName()"); + private static final Signature HASH_CODE = + TypeUtils.parseSignature("int hashCode()"); + private static final Signature EQUALS = + TypeUtils.parseSignature("boolean equals(Object)"); + private static final Signature STRING_LENGTH = + TypeUtils.parseSignature("int length()"); + private static final Signature STRING_CHAR_AT = + TypeUtils.parseSignature("char charAt(int)"); + private static final Signature FOR_NAME = + TypeUtils.parseSignature("Class forName(String)"); + private static final Signature DOUBLE_TO_LONG_BITS = + TypeUtils.parseSignature("long doubleToLongBits(double)"); + private static final Signature FLOAT_TO_INT_BITS = + TypeUtils.parseSignature("int floatToIntBits(float)"); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Signature APPEND_STRING = + TypeUtils.parseSignature("StringBuffer append(String)"); + private static final Signature APPEND_INT = + TypeUtils.parseSignature("StringBuffer append(int)"); + private static final Signature APPEND_DOUBLE = + TypeUtils.parseSignature("StringBuffer append(double)"); + private static final Signature APPEND_FLOAT = + TypeUtils.parseSignature("StringBuffer append(float)"); + private static final Signature APPEND_CHAR = + TypeUtils.parseSignature("StringBuffer append(char)"); + private static final Signature APPEND_LONG = + TypeUtils.parseSignature("StringBuffer append(long)"); + private static final Signature APPEND_BOOLEAN = + TypeUtils.parseSignature("StringBuffer append(boolean)"); + private static final Signature LENGTH = + TypeUtils.parseSignature("int length()"); + private static final Signature SET_LENGTH = + TypeUtils.parseSignature("void setLength(int)"); + private static final Signature GET_DECLARED_METHOD = + TypeUtils.parseSignature("java.lang.reflect.Method getDeclaredMethod(String, Class[])"); + + + + public static final ArrayDelimiters DEFAULT_DELIMITERS = new ArrayDelimiters("{", ", ", "}"); + + private EmitUtils() { + } + + public static void factory_method(ClassEmitter ce, Signature sig) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, sig, null); + e.new_instance_this(); + e.dup(); + e.load_args(); + e.invoke_constructor_this(TypeUtils.parseConstructor(sig.getArgumentTypes())); + e.return_value(); + e.end_method(); + } + + public static void null_constructor(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_NULL, null); + e.load_this(); + e.super_invoke_constructor(); + e.return_value(); + e.end_method(); + } + + /** + * Process an array on the stack. Assumes the top item on the stack + * is an array of the specified type. For each element in the array, + * puts the element on the stack and triggers the callback. + * @param type the type of the array (type.isArray() must be true) + * @param callback the callback triggered for each element + */ + public static void process_array(CodeEmitter e, Type type, ProcessArrayCallback callback) { + Type componentType = TypeUtils.getComponentType(type); + Local array = e.make_local(); + Local loopvar = e.make_local(Type.INT_TYPE); + Label loopbody = e.make_label(); + Label checkloop = e.make_label(); + e.store_local(array); + e.push(0); + e.store_local(loopvar); + e.goTo(checkloop); + + e.mark(loopbody); + e.load_local(array); + e.load_local(loopvar); + e.array_load(componentType); + callback.processElement(componentType); + e.iinc(loopvar, 1); + + e.mark(checkloop); + e.load_local(loopvar); + e.load_local(array); + e.arraylength(); + e.if_icmp(e.LT, loopbody); + } + + /** + * Process two arrays on the stack in parallel. Assumes the top two items on the stack + * are arrays of the specified class. The arrays must be the same length. For each pair + * of elements in the arrays, puts the pair on the stack and triggers the callback. + * @param type the type of the arrays (type.isArray() must be true) + * @param callback the callback triggered for each pair of elements + */ + public static void process_arrays(CodeEmitter e, Type type, ProcessArrayCallback callback) { + Type componentType = TypeUtils.getComponentType(type); + Local array1 = e.make_local(); + Local array2 = e.make_local(); + Local loopvar = e.make_local(Type.INT_TYPE); + Label loopbody = e.make_label(); + Label checkloop = e.make_label(); + e.store_local(array1); + e.store_local(array2); + e.push(0); + e.store_local(loopvar); + e.goTo(checkloop); + + e.mark(loopbody); + e.load_local(array1); + e.load_local(loopvar); + e.array_load(componentType); + e.load_local(array2); + e.load_local(loopvar); + e.array_load(componentType); + callback.processElement(componentType); + e.iinc(loopvar, 1); + + e.mark(checkloop); + e.load_local(loopvar); + e.load_local(array1); + e.arraylength(); + e.if_icmp(e.LT, loopbody); + } + + public static void string_switch(CodeEmitter e, String[] strings, int switchStyle, ObjectSwitchCallback callback) { + try { + switch (switchStyle) { + case Constants.SWITCH_STYLE_TRIE: + string_switch_trie(e, strings, callback); + break; + case Constants.SWITCH_STYLE_HASH: + string_switch_hash(e, strings, callback, false); + break; + case Constants.SWITCH_STYLE_HASHONLY: + string_switch_hash(e, strings, callback, true); + break; + default: + throw new IllegalArgumentException("unknown switch style " + switchStyle); + } + } catch (RuntimeException ex) { + throw ex; + } catch (Error ex) { + throw ex; + } catch (Exception ex) { + throw new CodeGenerationException(ex); + } + } + + private static void string_switch_trie(final CodeEmitter e, + String[] strings, + final ObjectSwitchCallback callback) throws Exception { + final Label def = e.make_label(); + final Label end = e.make_label(); + final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() { + public Object transform(Object value) { + return ((String)value).length(); + } + }); + e.dup(); + e.invoke_virtual(Constants.TYPE_STRING, STRING_LENGTH); + e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label ignore_end) throws Exception { + List bucket = (List)buckets.get(key); + stringSwitchHelper(e, bucket, callback, def, end, 0); + } + public void processDefault() { + e.goTo(def); + } + }); + e.mark(def); + e.pop(); + callback.processDefault(); + e.mark(end); + } + + private static void stringSwitchHelper(final CodeEmitter e, + List strings, + final ObjectSwitchCallback callback, + final Label def, + final Label end, + final int index) throws Exception { + final int len = ((String)strings.get(0)).length(); + final Map buckets = CollectionUtils.bucket(strings, new Transformer() { + public Object transform(Object value) { + return ((String)value).charAt(index); + } + }); + e.dup(); + e.push(index); + e.invoke_virtual(Constants.TYPE_STRING, STRING_CHAR_AT); + e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label ignore_end) throws Exception { + List bucket = (List)buckets.get(key); + if (index + 1 == len) { + e.pop(); + callback.processCase(bucket.get(0), end); + } else { + stringSwitchHelper(e, bucket, callback, def, end, index + 1); + } + } + public void processDefault() { + e.goTo(def); + } + }); + } + + static int[] getSwitchKeys(Map buckets) { + int[] keys = new int[buckets.size()]; + int index = 0; + for (Iterator it = buckets.keySet().iterator(); it.hasNext();) { + keys[index++] = ((Integer)it.next()); + } + Arrays.sort(keys); + return keys; + } + + private static void string_switch_hash(final CodeEmitter e, + final String[] strings, + final ObjectSwitchCallback callback, + final boolean skipEquals) throws Exception { + final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() { + public Object transform(Object value) { + return value.hashCode(); + } + }); + final Label def = e.make_label(); + final Label end = e.make_label(); + e.dup(); + e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE); + e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label ignore_end) throws Exception { + List bucket = (List)buckets.get(key); + Label next = null; + if (skipEquals && bucket.size() == 1) { + if (skipEquals) + e.pop(); + callback.processCase((String)bucket.get(0), end); + } else { + for (Iterator it = bucket.iterator(); it.hasNext();) { + String string = (String)it.next(); + if (next != null) { + e.mark(next); + } + if (it.hasNext()) { + e.dup(); + } + e.push(string); + e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS); + if (it.hasNext()) { + e.if_jump(e.EQ, next = e.make_label()); + e.pop(); + } else { + e.if_jump(e.EQ, def); + } + callback.processCase(string, end); + } + } + } + public void processDefault() { + e.pop(); + } + }); + e.mark(def); + callback.processDefault(); + e.mark(end); + } + + public static void load_class_this(CodeEmitter e) { + load_class_helper(e, e.getClassEmitter().getClassType()); + } + + public static void load_class(CodeEmitter e, Type type) { + if (TypeUtils.isPrimitive(type)) { + if (type == Type.VOID_TYPE) { + throw new IllegalArgumentException("cannot load void type"); + } + e.getstatic(TypeUtils.getBoxedType(type), "TYPE", Constants.TYPE_CLASS); + } else { + load_class_helper(e, type); + } + } + + private static void load_class_helper(CodeEmitter e, final Type type) { + if (e.isStaticHook()) { + // have to fall back on non-optimized load + e.push(TypeUtils.emulateClassGetName(type)); + e.invoke_static(Constants.TYPE_CLASS, FOR_NAME); + } else { + ClassEmitter ce = e.getClassEmitter(); + String typeName = TypeUtils.emulateClassGetName(type); + + // TODO: can end up with duplicated field names when using chained transformers; incorporate static hook # somehow + String fieldName = "CGLIB$load_class$" + TypeUtils.escapeType(typeName); + if (!ce.isFieldDeclared(fieldName)) { + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, fieldName, Constants.TYPE_CLASS, null); + CodeEmitter hook = ce.getStaticHook(); + hook.push(typeName); + hook.invoke_static(Constants.TYPE_CLASS, FOR_NAME); + hook.putstatic(ce.getClassType(), fieldName, Constants.TYPE_CLASS); + } + e.getfield(fieldName); + } + } + + public static void push_array(CodeEmitter e, Object[] array) { + e.push(array.length); + e.newarray(Type.getType(remapComponentType(array.getClass().getComponentType()))); + for (int i = 0; i < array.length; i++) { + e.dup(); + e.push(i); + push_object(e, array[i]); + e.aastore(); + } + } + + private static Class remapComponentType(Class componentType) { + if (componentType.equals(Type.class)) + return Class.class; + return componentType; + } + + public static void push_object(CodeEmitter e, Object obj) { + if (obj == null) { + e.aconst_null(); + } else { + Class type = obj.getClass(); + if (type.isArray()) { + push_array(e, (Object[])obj); + } else if (obj instanceof String) { + e.push((String)obj); + } else if (obj instanceof Type) { + load_class(e, (Type)obj); + } else if (obj instanceof Class) { + load_class(e, Type.getType((Class)obj)); + } else if (obj instanceof BigInteger) { + e.new_instance(Constants.TYPE_BIG_INTEGER); + e.dup(); + e.push(obj.toString()); + e.invoke_constructor(Constants.TYPE_BIG_INTEGER); + } else if (obj instanceof BigDecimal) { + e.new_instance(Constants.TYPE_BIG_DECIMAL); + e.dup(); + e.push(obj.toString()); + e.invoke_constructor(Constants.TYPE_BIG_DECIMAL); + } else { + throw new IllegalArgumentException("unknown type: " + obj.getClass()); + } + } + } + + /** + * @deprecated use {@link #hash_code(CodeEmitter, Type, int, CustomizerRegistry)} instead + */ + @Deprecated + public static void hash_code(CodeEmitter e, Type type, int multiplier, final Customizer customizer) { + hash_code(e, type, multiplier, CustomizerRegistry.singleton(customizer)); + } + + public static void hash_code(CodeEmitter e, Type type, int multiplier, final CustomizerRegistry registry) { + if (TypeUtils.isArray(type)) { + hash_array(e, type, multiplier, registry); + } else { + e.swap(Type.INT_TYPE, type); + e.push(multiplier); + e.math(e.MUL, Type.INT_TYPE); + e.swap(type, Type.INT_TYPE); + if (TypeUtils.isPrimitive(type)) { + hash_primitive(e, type); + } else { + hash_object(e, type, registry); + } + e.math(e.ADD, Type.INT_TYPE); + } + } + + private static void hash_array(final CodeEmitter e, Type type, final int multiplier, final CustomizerRegistry registry) { + Label skip = e.make_label(); + Label end = e.make_label(); + e.dup(); + e.ifnull(skip); + EmitUtils.process_array(e, type, new ProcessArrayCallback() { + public void processElement(Type type) { + hash_code(e, type, multiplier, registry); + } + }); + e.goTo(end); + e.mark(skip); + e.pop(); + e.mark(end); + } + + private static void hash_object(CodeEmitter e, Type type, CustomizerRegistry registry) { + // (f == null) ? 0 : f.hashCode(); + Label skip = e.make_label(); + Label end = e.make_label(); + e.dup(); + e.ifnull(skip); + boolean customHashCode = false; + for (HashCodeCustomizer customizer : registry.get(HashCodeCustomizer.class)) { + if (customizer.customize(e, type)) { + customHashCode = true; + break; + } + } + if (!customHashCode) { + for (Customizer customizer : registry.get(Customizer.class)) { + customizer.customize(e, type); + } + e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE); + } + e.goTo(end); + e.mark(skip); + e.pop(); + e.push(0); + e.mark(end); + } + + private static void hash_primitive(CodeEmitter e, Type type) { + switch (type.getSort()) { + case Type.BOOLEAN: + // f ? 0 : 1 + e.push(1); + e.math(e.XOR, Type.INT_TYPE); + break; + case Type.FLOAT: + // Float.floatToIntBits(f) + e.invoke_static(Constants.TYPE_FLOAT, FLOAT_TO_INT_BITS); + break; + case Type.DOUBLE: + // Double.doubleToLongBits(f), hash_code(Long.TYPE) + e.invoke_static(Constants.TYPE_DOUBLE, DOUBLE_TO_LONG_BITS); + // fall through + case Type.LONG: + hash_long(e); + } + } + + private static void hash_long(CodeEmitter e) { + // (int)(f ^ (f >>> 32)) + e.dup2(); + e.push(32); + e.math(e.USHR, Type.LONG_TYPE); + e.math(e.XOR, Type.LONG_TYPE); + e.cast_numeric(Type.LONG_TYPE, Type.INT_TYPE); + } + +// public static void not_equals(CodeEmitter e, Type type, Label notEquals) { +// not_equals(e, type, notEquals, null); +// } + + /** + * @deprecated use {@link #not_equals(CodeEmitter, Type, Label, CustomizerRegistry)} instead + */ + @Deprecated + public static void not_equals(CodeEmitter e, Type type, final Label notEquals, final Customizer customizer) { + not_equals(e, type, notEquals, CustomizerRegistry.singleton(customizer)); + } + + /** + * Branches to the specified label if the top two items on the stack + * are not equal. The items must both be of the specified + * class. Equality is determined by comparing primitive values + * directly and by invoking the equals method for + * Objects. Arrays are recursively processed in the same manner. + */ + public static void not_equals(final CodeEmitter e, Type type, final Label notEquals, final CustomizerRegistry registry) { + (new ProcessArrayCallback() { + public void processElement(Type type) { + not_equals_helper(e, type, notEquals, registry, this); + } + }).processElement(type); + } + + private static void not_equals_helper(CodeEmitter e, + Type type, + Label notEquals, + CustomizerRegistry registry, + ProcessArrayCallback callback) { + if (TypeUtils.isPrimitive(type)) { + e.if_cmp(type, e.NE, notEquals); + } else { + Label end = e.make_label(); + nullcmp(e, notEquals, end); + if (TypeUtils.isArray(type)) { + Label checkContents = e.make_label(); + e.dup2(); + e.arraylength(); + e.swap(); + e.arraylength(); + e.if_icmp(e.EQ, checkContents); + e.pop2(); + e.goTo(notEquals); + e.mark(checkContents); + EmitUtils.process_arrays(e, type, callback); + } else { + List customizers = registry.get(Customizer.class); + if (!customizers.isEmpty()) { + for (Customizer customizer : customizers) { + customizer.customize(e, type); + } + e.swap(); + for (Customizer customizer : customizers) { + customizer.customize(e, type); + } + } + e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS); + e.if_jump(e.EQ, notEquals); + } + e.mark(end); + } + } + + /** + * If both objects on the top of the stack are non-null, does nothing. + * If one is null, or both are null, both are popped off and execution + * branches to the respective label. + * @param oneNull label to branch to if only one of the objects is null + * @param bothNull label to branch to if both of the objects are null + */ + private static void nullcmp(CodeEmitter e, Label oneNull, Label bothNull) { + e.dup2(); + Label nonNull = e.make_label(); + Label oneNullHelper = e.make_label(); + Label end = e.make_label(); + e.ifnonnull(nonNull); + e.ifnonnull(oneNullHelper); + e.pop2(); + e.goTo(bothNull); + + e.mark(nonNull); + e.ifnull(oneNullHelper); + e.goTo(end); + + e.mark(oneNullHelper); + e.pop2(); + e.goTo(oneNull); + + e.mark(end); + } + + /* + public static void to_string(CodeEmitter e, + Type type, + ArrayDelimiters delims, + CustomizerRegistry registry) { + e.new_instance(Constants.TYPE_STRING_BUFFER); + e.dup(); + e.invoke_constructor(Constants.TYPE_STRING_BUFFER); + e.swap(); + append_string(e, type, delims, registry); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING); + } + */ + + /** + * @deprecated use {@link #append_string(CodeEmitter, Type, ArrayDelimiters, CustomizerRegistry)} instead + */ + @Deprecated + public static void append_string(final CodeEmitter e, + Type type, + final ArrayDelimiters delims, + final Customizer customizer) { + append_string(e, type, delims, CustomizerRegistry.singleton(customizer)); + } + + public static void append_string(final CodeEmitter e, + Type type, + final ArrayDelimiters delims, + final CustomizerRegistry registry) { + final ArrayDelimiters d = (delims != null) ? delims : DEFAULT_DELIMITERS; + ProcessArrayCallback callback = new ProcessArrayCallback() { + public void processElement(Type type) { + append_string_helper(e, type, d, registry, this); + e.push(d.inside); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + }; + append_string_helper(e, type, d, registry, callback); + } + + private static void append_string_helper(CodeEmitter e, + Type type, + ArrayDelimiters delims, + CustomizerRegistry registry, + ProcessArrayCallback callback) { + Label skip = e.make_label(); + Label end = e.make_label(); + if (TypeUtils.isPrimitive(type)) { + switch (type.getSort()) { + case Type.INT: + case Type.SHORT: + case Type.BYTE: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_INT); + break; + case Type.DOUBLE: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_DOUBLE); + break; + case Type.FLOAT: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_FLOAT); + break; + case Type.LONG: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_LONG); + break; + case Type.BOOLEAN: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_BOOLEAN); + break; + case Type.CHAR: + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_CHAR); + break; + } + } else if (TypeUtils.isArray(type)) { + e.dup(); + e.ifnull(skip); + e.swap(); + if (delims != null && delims.before != null && !"".equals(delims.before)) { + e.push(delims.before); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + e.swap(); + } + EmitUtils.process_array(e, type, callback); + shrinkStringBuffer(e, 2); + if (delims != null && delims.after != null && !"".equals(delims.after)) { + e.push(delims.after); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + } else { + e.dup(); + e.ifnull(skip); + for (Customizer customizer : registry.get(Customizer.class)) { + customizer.customize(e, type); + } + e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + e.goTo(end); + e.mark(skip); + e.pop(); + e.push("null"); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + e.mark(end); + } + + private static void shrinkStringBuffer(CodeEmitter e, int amt) { + e.dup(); + e.dup(); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, LENGTH); + e.push(amt); + e.math(e.SUB, Type.INT_TYPE); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, SET_LENGTH); + } + + public static class ArrayDelimiters { + private String before; + private String inside; + private String after; + + public ArrayDelimiters(String before, String inside, String after) { + this.before = before; + this.inside = inside; + this.after = after; + } + } + + public static void load_method(CodeEmitter e, MethodInfo method) { + load_class(e, method.getClassInfo().getType()); + e.push(method.getSignature().getName()); + push_object(e, method.getSignature().getArgumentTypes()); + e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHOD); + } + + private interface ParameterTyper { + Type[] getParameterTypes(MethodInfo member); + } + + public static void method_switch(CodeEmitter e, + List methods, + ObjectSwitchCallback callback) { + member_switch_helper(e, methods, callback, true); + } + + public static void constructor_switch(CodeEmitter e, + List constructors, + ObjectSwitchCallback callback) { + member_switch_helper(e, constructors, callback, false); + } + + private static void member_switch_helper(final CodeEmitter e, + List members, + final ObjectSwitchCallback callback, + boolean useName) { + try { + final Map cache = new HashMap(); + final ParameterTyper cached = new ParameterTyper() { + public Type[] getParameterTypes(MethodInfo member) { + Type[] types = (Type[])cache.get(member); + if (types == null) { + cache.put(member, types = member.getSignature().getArgumentTypes()); + } + return types; + } + }; + final Label def = e.make_label(); + final Label end = e.make_label(); + if (useName) { + e.swap(); + final Map buckets = CollectionUtils.bucket(members, new Transformer() { + public Object transform(Object value) { + return ((MethodInfo)value).getSignature().getName(); + } + }); + String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label dontUseEnd) throws Exception { + member_helper_size(e, (List)buckets.get(key), callback, cached, def, end); + } + public void processDefault() throws Exception { + e.goTo(def); + } + }); + } else { + member_helper_size(e, members, callback, cached, def, end); + } + e.mark(def); + e.pop(); + callback.processDefault(); + e.mark(end); + } catch (RuntimeException ex) { + throw ex; + } catch (Error ex) { + throw ex; + } catch (Exception ex) { + throw new CodeGenerationException(ex); + } + } + + private static void member_helper_size(final CodeEmitter e, + List members, + final ObjectSwitchCallback callback, + final ParameterTyper typer, + final Label def, + final Label end) throws Exception { + final Map buckets = CollectionUtils.bucket(members, new Transformer() { + public Object transform(Object value) { + return typer.getParameterTypes((MethodInfo)value).length; + } + }); + e.dup(); + e.arraylength(); + e.process_switch(EmitUtils.getSwitchKeys(buckets), new ProcessSwitchCallback() { + public void processCase(int key, Label dontUseEnd) throws Exception { + List bucket = (List)buckets.get(key); + member_helper_type(e, bucket, callback, typer, def, end, new BitSet()); + } + public void processDefault() throws Exception { + e.goTo(def); + } + }); + } + + private static void member_helper_type(final CodeEmitter e, + List members, + final ObjectSwitchCallback callback, + final ParameterTyper typer, + final Label def, + final Label end, + final BitSet checked) throws Exception { + if (members.size() == 1) { + MethodInfo member = (MethodInfo)members.get(0); + Type[] types = typer.getParameterTypes(member); + // need to check classes that have not already been checked via switches + for (int i = 0; i < types.length; i++) { + if (checked == null || !checked.get(i)) { + e.dup(); + e.aaload(i); + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + e.push(TypeUtils.emulateClassGetName(types[i])); + e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS); + e.if_jump(e.EQ, def); + } + } + e.pop(); + callback.processCase(member, end); + } else { + // choose the index that has the best chance of uniquely identifying member + Type[] example = typer.getParameterTypes((MethodInfo)members.get(0)); + Map buckets = null; + int index = -1; + for (int i = 0; i < example.length; i++) { + final int j = i; + Map test = CollectionUtils.bucket(members, new Transformer() { + public Object transform(Object value) { + return TypeUtils.emulateClassGetName(typer.getParameterTypes((MethodInfo)value)[j]); + } + }); + if (buckets == null || test.size() > buckets.size()) { + buckets = test; + index = i; + } + } + if (buckets == null || buckets.size() == 1) { + // TODO: switch by returnType + // must have two methods with same name, types, and different return types + e.goTo(def); + } else { + checked.set(index); + + e.dup(); + e.aaload(index); + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + + final Map fbuckets = buckets; + String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label dontUseEnd) throws Exception { + member_helper_type(e, (List)fbuckets.get(key), callback, typer, def, end, checked); + } + public void processDefault() throws Exception { + e.goTo(def); + } + }); + } + } + } + + public static void wrap_throwable(Block block, Type wrapper) { + CodeEmitter e = block.getCodeEmitter(); + e.catch_exception(block, Constants.TYPE_THROWABLE); + e.new_instance(wrapper); + e.dup_x1(); + e.swap(); + e.invoke_constructor(wrapper, CSTRUCT_THROWABLE); + e.athrow(); + } + + public static void add_properties(ClassEmitter ce, String[] names, Type[] types) { + for (int i = 0; i < names.length; i++) { + String fieldName = "$cglib_prop_" + names[i]; + ce.declare_field(Constants.ACC_PRIVATE, fieldName, types[i], null); + EmitUtils.add_property(ce, names[i], types[i], fieldName); + } + } + + public static void add_property(ClassEmitter ce, String name, Type type, String fieldName) { + String property = TypeUtils.upperFirst(name); + CodeEmitter e; + e = ce.begin_method(Constants.ACC_PUBLIC, + new Signature("get" + property, + type, + Constants.TYPES_EMPTY), + null); + e.load_this(); + e.getfield(fieldName); + e.return_value(); + e.end_method(); + + e = ce.begin_method(Constants.ACC_PUBLIC, + new Signature("set" + property, + Type.VOID_TYPE, + new Type[]{ type }), + null); + e.load_this(); + e.load_arg(0); + e.putfield(fieldName); + e.return_value(); + e.end_method(); + } + + /* generates: + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch ( e) { + throw e; + } catch (Throwable e) { + throw new (e); + } + */ + public static void wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[] exceptions, Type wrapper) { + Set set = (exceptions == null) ? Collections.EMPTY_SET : new HashSet(Arrays.asList(exceptions)); + + if (set.contains(Constants.TYPE_THROWABLE)) + return; + + boolean needThrow = exceptions != null; + if (!set.contains(Constants.TYPE_RUNTIME_EXCEPTION)) { + e.catch_exception(handler, Constants.TYPE_RUNTIME_EXCEPTION); + needThrow = true; + } + if (!set.contains(Constants.TYPE_ERROR)) { + e.catch_exception(handler, Constants.TYPE_ERROR); + needThrow = true; + } + if (exceptions != null) { + for (int i = 0; i < exceptions.length; i++) { + e.catch_exception(handler, exceptions[i]); + } + } + if (needThrow) { + e.athrow(); + } + // e -> eo -> oeo -> ooe -> o + e.catch_exception(handler, Constants.TYPE_THROWABLE); + e.new_instance(wrapper); + e.dup_x1(); + e.swap(); + e.invoke_constructor(wrapper, CSTRUCT_THROWABLE); + e.athrow(); + } + + public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method) { + return begin_method(e, method, method.getModifiers()); + } + + public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method, int access) { + return e.begin_method(access, + method.getSignature(), + method.getExceptionTypes()); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/FieldTypeCustomizer.java b/spring-core/src/main/java/org/springframework/cglib/core/FieldTypeCustomizer.java new file mode 100644 index 0000000000..d8d90c26c0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/FieldTypeCustomizer.java @@ -0,0 +1,23 @@ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +/** + * Customizes key types for {@link KeyFactory} right in constructor. + */ +public interface FieldTypeCustomizer extends KeyFactoryCustomizer { + /** + * Customizes {@code this.FIELD_0 = ?} assignment in key constructor + * @param e code emitter + * @param index parameter index + * @param type parameter type + */ + void customize(CodeEmitter e, int index, Type type); + + /** + * Computes type of field for storing given parameter + * @param index parameter index + * @param type parameter type + */ + Type getOutType(int index, Type type); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java b/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java new file mode 100644 index 0000000000..5152fc9030 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/GeneratorStrategy.java @@ -0,0 +1,44 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +/** + * The GeneratorStrategyClass. By providing your + * own strategy you may examine or modify the generated class before + * it is loaded. Typically this will be accomplished by subclassing + * {@link DefaultGeneratorStrategy} and overriding the appropriate + * protected method. + * @see AbstractClassGenerator#setStrategy + */ +public interface GeneratorStrategy { + /** + * Generate the class. + * @param cg a class generator on which you can call {@link ClassGenerator#generateClass} + * @return a byte array containing the bits of a valid Class + */ + byte[] generate(ClassGenerator cg) throws Exception; + + /** + * The GeneratorStrategy in use does not currently, but may + * in the future, affect the caching of classes generated by {@link + * AbstractClassGenerator}, so this is a reminder that you should + * correctly implement equals and hashCode + * to avoid generating too many classes. + */ + boolean equals(Object o); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/HashCodeCustomizer.java b/spring-core/src/main/java/org/springframework/cglib/core/HashCodeCustomizer.java new file mode 100644 index 0000000000..21899023d4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/HashCodeCustomizer.java @@ -0,0 +1,12 @@ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +public interface HashCodeCustomizer extends KeyFactoryCustomizer { + /** + * Customizes calculation of hashcode + * @param e code emitter + * @param type parameter type + */ + boolean customize(CodeEmitter e, Type type); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/KeyFactoryCustomizer.java b/spring-core/src/main/java/org/springframework/cglib/core/KeyFactoryCustomizer.java new file mode 100644 index 0000000000..54f23bf679 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/KeyFactoryCustomizer.java @@ -0,0 +1,7 @@ +package org.springframework.cglib.core; + +/** + * Marker interface for customizers of {@link KeyFactory} + */ +public interface KeyFactoryCustomizer { +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Local.java b/spring-core/src/main/java/org/springframework/cglib/core/Local.java new file mode 100644 index 0000000000..93c2d22f35 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Local.java @@ -0,0 +1,37 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +public class Local +{ + private Type type; + private int index; + + public Local(int index, Type type) { + this.type = type; + this.index = index; + } + + public int getIndex() { + return index; + } + + public Type getType() { + return type; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java b/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java new file mode 100644 index 0000000000..d3cdd9d548 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/LocalVariablesSorter.java @@ -0,0 +1,158 @@ +/*** + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2005 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Label; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; + +/** + * A {@link MethodVisitor} that renumbers local variables in their order of + * appearance. This adapter allows one to easily add new local variables to a + * method. + * + * @author Chris Nokleberg + * @author Eric Bruneton + */ +public class LocalVariablesSorter extends MethodVisitor { + + /** + * Mapping from old to new local variable indexes. A local variable at index + * i of size 1 is remapped to 'mapping[2*i]', while a local variable at + * index i of size 2 is remapped to 'mapping[2*i+1]'. + */ + private static class State + { + int[] mapping = new int[40]; + int nextLocal; + } + + protected final int firstLocal; + private final State state; + + public LocalVariablesSorter( + final int access, + final String desc, + final MethodVisitor mv) + { + super(Constants.ASM_API, mv); + state = new State(); + Type[] args = Type.getArgumentTypes(desc); + state.nextLocal = ((Opcodes.ACC_STATIC & access) != 0) ? 0 : 1; + for (int i = 0; i < args.length; i++) { + state.nextLocal += args[i].getSize(); + } + firstLocal = state.nextLocal; + } + + public LocalVariablesSorter(LocalVariablesSorter lvs) { + super(Constants.ASM_API, lvs.mv); + state = lvs.state; + firstLocal = lvs.firstLocal; + } + + public void visitVarInsn(final int opcode, final int var) { + int size; + switch (opcode) { + case Opcodes.LLOAD: + case Opcodes.LSTORE: + case Opcodes.DLOAD: + case Opcodes.DSTORE: + size = 2; + break; + default: + size = 1; + } + mv.visitVarInsn(opcode, remap(var, size)); + } + + public void visitIincInsn(final int var, final int increment) { + mv.visitIincInsn(remap(var, 1), increment); + } + + public void visitMaxs(final int maxStack, final int maxLocals) { + mv.visitMaxs(maxStack, state.nextLocal); + } + + public void visitLocalVariable( + final String name, + final String desc, + final String signature, + final Label start, + final Label end, + final int index) + { + mv.visitLocalVariable(name, desc, signature, start, end, remap(index)); + } + + // ------------- + + protected int newLocal(final int size) { + int var = state.nextLocal; + state.nextLocal += size; + return var; + } + + private int remap(final int var, final int size) { + if (var < firstLocal) { + return var; + } + int key = 2 * var + size - 1; + int length = state.mapping.length; + if (key >= length) { + int[] newMapping = new int[Math.max(2 * length, key + 1)]; + System.arraycopy(state.mapping, 0, newMapping, 0, length); + state.mapping = newMapping; + } + int value = state.mapping[key]; + if (value == 0) { + value = state.nextLocal + 1; + state.mapping[key] = value; + state.nextLocal += size; + } + return value - 1; + } + + private int remap(final int var) { + if (var < firstLocal) { + return var; + } + int key = 2 * var; + int value = key < state.mapping.length ? state.mapping[key] : 0; + if (value == 0) { + value = key + 1 < state.mapping.length ? state.mapping[key + 1] : 0; + } + if (value == 0) { + throw new IllegalStateException("Unknown local variable " + var); + } + return value - 1; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java b/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java new file mode 100644 index 0000000000..2f54aea276 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/MethodInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; + +abstract public class MethodInfo { + + protected MethodInfo() { + } + + abstract public ClassInfo getClassInfo(); + abstract public int getModifiers(); + abstract public Signature getSignature(); + abstract public Type[] getExceptionTypes(); + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof MethodInfo)) + return false; + return getSignature().equals(((MethodInfo)o).getSignature()); + } + + public int hashCode() { + return getSignature().hashCode(); + } + + public String toString() { + // TODO: include modifiers, exceptions + return getSignature().toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/MethodInfoTransformer.java b/spring-core/src/main/java/org/springframework/cglib/core/MethodInfoTransformer.java new file mode 100644 index 0000000000..2ddad07de8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/MethodInfoTransformer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.lang.reflect.*; + +public class MethodInfoTransformer implements Transformer +{ + private static final MethodInfoTransformer INSTANCE = new MethodInfoTransformer(); + + public static MethodInfoTransformer getInstance() { + return INSTANCE; + } + + public Object transform(Object value) { + if (value instanceof Method) { + return ReflectUtils.getMethodInfo((Method)value); + } else if (value instanceof Constructor) { + return ReflectUtils.getMethodInfo((Constructor)value); + } else { + throw new IllegalArgumentException("cannot get method info for " + value); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/NamingPolicy.java b/spring-core/src/main/java/org/springframework/cglib/core/NamingPolicy.java new file mode 100644 index 0000000000..152e891641 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/NamingPolicy.java @@ -0,0 +1,43 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.util.Set; + +/** + * Customize the generated class name for {@link AbstractClassGenerator}-based utilities. + */ +public interface NamingPolicy { + /** + * Choose a name for a generated class. + * @param prefix a dotted-name chosen by the generating class (possibly to put the generated class in a particular package) + * @param source the fully-qualified class name of the generating class (for example "org.springframework.cglib.Enhancer") + * @param key A key object representing the state of the parameters; for caching to work properly, equal keys should result + * in the same generated class name. The default policy incorporates key.hashCode() into the class name. + * @param names a predicate that returns true if the given classname has already been used in the same ClassLoader. + * @return the fully-qualified class name + */ + String getClassName(String prefix, String source, Object key, Predicate names); + + /** + * The NamingPolicy in use does not currently, but may + * in the future, affect the caching of classes generated by {@link + * AbstractClassGenerator}, so this is a reminder that you should + * correctly implement equals and hashCode + * to avoid generating too many classes. + */ + boolean equals(Object o); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ObjectSwitchCallback.java b/spring-core/src/main/java/org/springframework/cglib/core/ObjectSwitchCallback.java new file mode 100644 index 0000000000..0fc3496913 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ObjectSwitchCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Label; + +public interface ObjectSwitchCallback { + void processCase(Object key, Label end) throws Exception; + void processDefault() throws Exception; +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java b/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java new file mode 100644 index 0000000000..dad9ae37b9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Predicate.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +public interface Predicate { + boolean evaluate(Object arg); +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ProcessArrayCallback.java b/spring-core/src/main/java/org/springframework/cglib/core/ProcessArrayCallback.java new file mode 100644 index 0000000000..da8eadbdfb --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ProcessArrayCallback.java @@ -0,0 +1,22 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +public interface ProcessArrayCallback { + void processElement(Type type); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ProcessSwitchCallback.java b/spring-core/src/main/java/org/springframework/cglib/core/ProcessSwitchCallback.java new file mode 100644 index 0000000000..1178dcb98e --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ProcessSwitchCallback.java @@ -0,0 +1,23 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Label; + +public interface ProcessSwitchCallback { + void processCase(int key, Label end) throws Exception; + void processDefault() throws Exception; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java b/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java new file mode 100644 index 0000000000..5bcdb3ae00 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/RejectModifierPredicate.java @@ -0,0 +1,30 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.lang.reflect.*; + +public class RejectModifierPredicate implements Predicate { + private int rejectMask; + + public RejectModifierPredicate(int rejectMask) { + this.rejectMask = rejectMask; + } + + public boolean evaluate(Object arg) { + return (((Member)arg).getModifiers() & rejectMask) == 0; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Signature.java b/spring-core/src/main/java/org/springframework/cglib/core/Signature.java new file mode 100644 index 0000000000..ab040ad69c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Signature.java @@ -0,0 +1,73 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import org.springframework.asm.Type; + +/** + * A representation of a method signature, containing the method name, + * return type, and parameter types. + */ +public class Signature { + private String name; + private String desc; + + public Signature(String name, String desc) { + // TODO: better error checking + if (name.indexOf('(') >= 0) { + throw new IllegalArgumentException("Name '" + name + "' is invalid"); + } + this.name = name; + this.desc = desc; + } + + public Signature(String name, Type returnType, Type[] argumentTypes) { + this(name, Type.getMethodDescriptor(returnType, argumentTypes)); + } + + public String getName() { + return name; + } + + public String getDescriptor() { + return desc; + } + + public Type getReturnType() { + return Type.getReturnType(desc); + } + + public Type[] getArgumentTypes() { + return Type.getArgumentTypes(desc); + } + + public String toString() { + return name + desc; + } + + public boolean equals(Object o) { + if (o == null) + return false; + if (!(o instanceof Signature)) + return false; + Signature other = (Signature)o; + return name.equals(other.name) && desc.equals(other.desc); + } + + public int hashCode() { + return name.hashCode() ^ desc.hashCode(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/TinyBitSet.java b/spring-core/src/main/java/org/springframework/cglib/core/TinyBitSet.java new file mode 100644 index 0000000000..52a273fd34 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/TinyBitSet.java @@ -0,0 +1,84 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +@Deprecated +public class TinyBitSet { + private static int[] T = new int[256]; + private int value = 0; + + private static int gcount(int x) { + int c = 0; + while (x != 0) { + c++; + x &= (x - 1); + } + return c; + } + + static { + for (int j = 0; j < 256; j++) { + T[j] = gcount(j); + } + } + + private static int topbit(int i) { + int j; + for (j = 0; i != 0; i ^= j) { + j = i & -i; + } + return j; + } + + private static int log2(int i) { + int j = 0; + for (j = 0; i != 0; i >>= 1) { + j++; + } + return j; + } + + public int length() { + return log2(topbit(value)); + } + + /** + * If bit 31 is set then this method results in an infinite loop. + * + * @return the number of bits set to true in this TinyBitSet. + */ + public int cardinality() { + int w = value; + int c = 0; + while (w != 0) { + c += T[w & 255]; + w >>= 8; + } + return c; + } + + public boolean get(int index) { + return (value & (1 << index)) != 0; + } + + public void set(int index) { + value |= (1 << index); + } + + public void clear(int index) { + value &= ~(1 << index); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/Transformer.java b/spring-core/src/main/java/org/springframework/cglib/core/Transformer.java new file mode 100644 index 0000000000..cdbfef8473 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/Transformer.java @@ -0,0 +1,20 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +public interface Transformer { + Object transform(Object value); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java new file mode 100644 index 0000000000..1a34e246cf --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/TypeUtils.java @@ -0,0 +1,422 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.util.*; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class TypeUtils { + private static final Map transforms = new HashMap(); + private static final Map rtransforms = new HashMap(); + + private TypeUtils() { + } + + static { + transforms.put("void", "V"); + transforms.put("byte", "B"); + transforms.put("char", "C"); + transforms.put("double", "D"); + transforms.put("float", "F"); + transforms.put("int", "I"); + transforms.put("long", "J"); + transforms.put("short", "S"); + transforms.put("boolean", "Z"); + + CollectionUtils.reverse(transforms, rtransforms); + } + + public static Type getType(String className) { + return Type.getType("L" + className.replace('.', '/') + ";"); + } + + public static boolean isFinal(int access) { + return (Constants.ACC_FINAL & access) != 0; + } + + public static boolean isStatic(int access) { + return (Constants.ACC_STATIC & access) != 0; + } + + public static boolean isProtected(int access) { + return (Constants.ACC_PROTECTED & access) != 0; + } + + public static boolean isPublic(int access) { + return (Constants.ACC_PUBLIC & access) != 0; + } + + public static boolean isAbstract(int access) { + return (Constants.ACC_ABSTRACT & access) != 0; + } + + public static boolean isInterface(int access) { + return (Constants.ACC_INTERFACE & access) != 0; + } + + public static boolean isPrivate(int access) { + return (Constants.ACC_PRIVATE & access) != 0; + } + + public static boolean isSynthetic(int access) { + return (Constants.ACC_SYNTHETIC & access) != 0; + } + + public static boolean isBridge(int access) { + return (Constants.ACC_BRIDGE & access) != 0; + } + + // getPackage returns null on JDK 1.2 + public static String getPackageName(Type type) { + return getPackageName(getClassName(type)); + } + + public static String getPackageName(String className) { + int idx = className.lastIndexOf('.'); + return (idx < 0) ? "" : className.substring(0, idx); + } + + public static String upperFirst(String s) { + if (s == null || s.length() == 0) { + return s; + } + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } + + public static String getClassName(Type type) { + if (isPrimitive(type)) { + return (String)rtransforms.get(type.getDescriptor()); + } else if (isArray(type)) { + return getClassName(getComponentType(type)) + "[]"; + } else { + return type.getClassName(); + } + } + + public static Type[] add(Type[] types, Type extra) { + if (types == null) { + return new Type[]{ extra }; + } else { + List list = Arrays.asList(types); + if (list.contains(extra)) { + return types; + } + Type[] copy = new Type[types.length + 1]; + System.arraycopy(types, 0, copy, 0, types.length); + copy[types.length] = extra; + return copy; + } + } + + public static Type[] add(Type[] t1, Type[] t2) { + // TODO: set semantics? + Type[] all = new Type[t1.length + t2.length]; + System.arraycopy(t1, 0, all, 0, t1.length); + System.arraycopy(t2, 0, all, t1.length, t2.length); + return all; + } + + public static Type fromInternalName(String name) { + // TODO; primitives? + return Type.getType("L" + name + ";"); + } + + public static Type[] fromInternalNames(String[] names) { + if (names == null) { + return null; + } + Type[] types = new Type[names.length]; + for (int i = 0; i < names.length; i++) { + types[i] = fromInternalName(names[i]); + } + return types; + } + + public static int getStackSize(Type[] types) { + int size = 0; + for (int i = 0; i < types.length; i++) { + size += types[i].getSize(); + } + return size; + } + + public static String[] toInternalNames(Type[] types) { + if (types == null) { + return null; + } + String[] names = new String[types.length]; + for (int i = 0; i < types.length; i++) { + names[i] = types[i].getInternalName(); + } + return names; + } + + public static Signature parseSignature(String s) { + int space = s.indexOf(' '); + int lparen = s.indexOf('(', space); + int rparen = s.indexOf(')', lparen); + String returnType = s.substring(0, space); + String methodName = s.substring(space + 1, lparen); + StringBuffer sb = new StringBuffer(); + sb.append('('); + for (Iterator it = parseTypes(s, lparen + 1, rparen).iterator(); it.hasNext();) { + sb.append(it.next()); + } + sb.append(')'); + sb.append(map(returnType)); + return new Signature(methodName, sb.toString()); + } + + public static Type parseType(String s) { + return Type.getType(map(s)); + } + + public static Type[] parseTypes(String s) { + List names = parseTypes(s, 0, s.length()); + Type[] types = new Type[names.size()]; + for (int i = 0; i < types.length; i++) { + types[i] = Type.getType((String)names.get(i)); + } + return types; + } + + public static Signature parseConstructor(Type[] types) { + StringBuffer sb = new StringBuffer(); + sb.append("("); + for (int i = 0; i < types.length; i++) { + sb.append(types[i].getDescriptor()); + } + sb.append(")"); + sb.append("V"); + return new Signature(Constants.CONSTRUCTOR_NAME, sb.toString()); + } + + public static Signature parseConstructor(String sig) { + return parseSignature("void (" + sig + ")"); // TODO + } + + private static List parseTypes(String s, int mark, int end) { + List types = new ArrayList(5); + for (;;) { + int next = s.indexOf(',', mark); + if (next < 0) { + break; + } + types.add(map(s.substring(mark, next).trim())); + mark = next + 1; + } + types.add(map(s.substring(mark, end).trim())); + return types; + } + + private static String map(String type) { + if (type.equals("")) { + return type; + } + String t = (String)transforms.get(type); + if (t != null) { + return t; + } else if (type.indexOf('.') < 0) { + return map("java.lang." + type); + } else { + StringBuffer sb = new StringBuffer(); + int index = 0; + while ((index = type.indexOf("[]", index) + 1) > 0) { + sb.append('['); + } + type = type.substring(0, type.length() - sb.length() * 2); + sb.append('L').append(type.replace('.', '/')).append(';'); + return sb.toString(); + } + } + + public static Type getBoxedType(Type type) { + switch (type.getSort()) { + case Type.CHAR: + return Constants.TYPE_CHARACTER; + case Type.BOOLEAN: + return Constants.TYPE_BOOLEAN; + case Type.DOUBLE: + return Constants.TYPE_DOUBLE; + case Type.FLOAT: + return Constants.TYPE_FLOAT; + case Type.LONG: + return Constants.TYPE_LONG; + case Type.INT: + return Constants.TYPE_INTEGER; + case Type.SHORT: + return Constants.TYPE_SHORT; + case Type.BYTE: + return Constants.TYPE_BYTE; + default: + return type; + } + } + + public static Type getUnboxedType(Type type) { + if (Constants.TYPE_INTEGER.equals(type)) { + return Type.INT_TYPE; + } else if (Constants.TYPE_BOOLEAN.equals(type)) { + return Type.BOOLEAN_TYPE; + } else if (Constants.TYPE_DOUBLE.equals(type)) { + return Type.DOUBLE_TYPE; + } else if (Constants.TYPE_LONG.equals(type)) { + return Type.LONG_TYPE; + } else if (Constants.TYPE_CHARACTER.equals(type)) { + return Type.CHAR_TYPE; + } else if (Constants.TYPE_BYTE.equals(type)) { + return Type.BYTE_TYPE; + } else if (Constants.TYPE_FLOAT.equals(type)) { + return Type.FLOAT_TYPE; + } else if (Constants.TYPE_SHORT.equals(type)) { + return Type.SHORT_TYPE; + } else { + return type; + } + } + + public static boolean isArray(Type type) { + return type.getSort() == Type.ARRAY; + } + + public static Type getComponentType(Type type) { + if (!isArray(type)) { + throw new IllegalArgumentException("Type " + type + " is not an array"); + } + return Type.getType(type.getDescriptor().substring(1)); + } + + public static boolean isPrimitive(Type type) { + switch (type.getSort()) { + case Type.ARRAY: + case Type.OBJECT: + return false; + default: + return true; + } + } + + public static String emulateClassGetName(Type type) { + if (isArray(type)) { + return type.getDescriptor().replace('/', '.'); + } else { + return getClassName(type); + } + } + + public static boolean isConstructor(MethodInfo method) { + return method.getSignature().getName().equals(Constants.CONSTRUCTOR_NAME); + } + + public static Type[] getTypes(Class[] classes) { + if (classes == null) { + return null; + } + Type[] types = new Type[classes.length]; + for (int i = 0; i < classes.length; i++) { + types[i] = Type.getType(classes[i]); + } + return types; + } + + public static int ICONST(int value) { + switch (value) { + case -1: return Constants.ICONST_M1; + case 0: return Constants.ICONST_0; + case 1: return Constants.ICONST_1; + case 2: return Constants.ICONST_2; + case 3: return Constants.ICONST_3; + case 4: return Constants.ICONST_4; + case 5: return Constants.ICONST_5; + } + return -1; // error + } + + public static int LCONST(long value) { + if (value == 0L) { + return Constants.LCONST_0; + } else if (value == 1L) { + return Constants.LCONST_1; + } else { + return -1; // error + } + } + + public static int FCONST(float value) { + if (value == 0f) { + return Constants.FCONST_0; + } else if (value == 1f) { + return Constants.FCONST_1; + } else if (value == 2f) { + return Constants.FCONST_2; + } else { + return -1; // error + } + } + + public static int DCONST(double value) { + if (value == 0d) { + return Constants.DCONST_0; + } else if (value == 1d) { + return Constants.DCONST_1; + } else { + return -1; // error + } + } + + public static int NEWARRAY(Type type) { + switch (type.getSort()) { + case Type.BYTE: + return Constants.T_BYTE; + case Type.CHAR: + return Constants.T_CHAR; + case Type.DOUBLE: + return Constants.T_DOUBLE; + case Type.FLOAT: + return Constants.T_FLOAT; + case Type.INT: + return Constants.T_INT; + case Type.LONG: + return Constants.T_LONG; + case Type.SHORT: + return Constants.T_SHORT; + case Type.BOOLEAN: + return Constants.T_BOOLEAN; + default: + return -1; // error + } + } + + public static String escapeType(String s) { + StringBuffer sb = new StringBuffer(); + for (int i = 0, len = s.length(); i < len; i++) { + char c = s.charAt(i); + switch (c) { + case '$': sb.append("$24"); break; + case '.': sb.append("$2E"); break; + case '[': sb.append("$5B"); break; + case ';': sb.append("$3B"); break; + case '(': sb.append("$28"); break; + case ')': sb.append("$29"); break; + case '/': sb.append("$2F"); break; + default: + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java b/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java new file mode 100644 index 0000000000..efd96c70e8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/VisibilityPredicate.java @@ -0,0 +1,53 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.core; + +import java.lang.reflect.*; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class VisibilityPredicate implements Predicate { + private boolean protectedOk; + private String pkg; + private boolean samePackageOk; + + public VisibilityPredicate(Class source, boolean protectedOk) { + this.protectedOk = protectedOk; + // same package is not ok for the bootstrap loaded classes. In all other cases we are + // generating classes in the same classloader + this.samePackageOk = source.getClassLoader() != null; + pkg = TypeUtils.getPackageName(Type.getType(source)); + } + + public boolean evaluate(Object arg) { + Member member = (Member)arg; + int mod = member.getModifiers(); + if (Modifier.isPrivate(mod)) { + return false; + } else if (Modifier.isPublic(mod)) { + return true; + } else if (Modifier.isProtected(mod) && protectedOk) { + // protected is fine if 'protectedOk' is true (for subclasses) + return true; + } else { + // protected/package private if the member is in the same package as the source class + // and we are generating into the same classloader. + return samePackageOk + && pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass()))); + } + } +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/core/WeakCacheKey.java b/spring-core/src/main/java/org/springframework/cglib/core/WeakCacheKey.java new file mode 100644 index 0000000000..808416745d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/WeakCacheKey.java @@ -0,0 +1,42 @@ +package org.springframework.cglib.core; + +import java.lang.ref.WeakReference; + +/** + * Allows to check for object equality, yet the class does not keep strong reference to the target. + * {@link #equals(Object)} returns true if and only if the reference is not yet expired and target + * objects are equal in terms of {@link #equals(Object)}. + *

+ * This an internal class, thus it might disappear in future cglib releases. + * + * @param type of the reference + */ +public class WeakCacheKey extends WeakReference { + private final int hash; + + public WeakCacheKey(T referent) { + super(referent); + this.hash = referent.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof WeakCacheKey)) { + return false; + } + Object ours = get(); + Object theirs = ((WeakCacheKey) obj).get(); + return ours != null && theirs != null && ours.equals(theirs); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + T t = get(); + return t == null ? "Clean WeakIdentityKey, hash: " + hash : t.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java b/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java new file mode 100644 index 0000000000..86ef5bff06 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/internal/CustomizerRegistry.java @@ -0,0 +1,49 @@ +package org.springframework.cglib.core.internal; + +import org.springframework.cglib.core.Customizer; +import org.springframework.cglib.core.FieldTypeCustomizer; +import org.springframework.cglib.core.KeyFactoryCustomizer; + +import java.util.*; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class CustomizerRegistry { + private final Class[] customizerTypes; + private Map> customizers = new HashMap>(); + + public CustomizerRegistry(Class[] customizerTypes) { + this.customizerTypes = customizerTypes; + } + + public void add(KeyFactoryCustomizer customizer) { + Class klass = customizer.getClass(); + for (Class type : customizerTypes) { + if (type.isAssignableFrom(klass)) { + List list = customizers.get(type); + if (list == null) { + customizers.put(type, list = new ArrayList()); + } + list.add(customizer); + } + } + } + + public List get(Class klass) { + List list = customizers.get(klass); + if (list == null) { + return Collections.emptyList(); + } + return (List) list; + } + + /** + * @deprecated Only to keep backward compatibility. + */ + @Deprecated + public static CustomizerRegistry singleton(Customizer customizer) + { + CustomizerRegistry registry = new CustomizerRegistry(new Class[]{Customizer.class}); + registry.add(customizer); + return registry; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/internal/Function.java b/spring-core/src/main/java/org/springframework/cglib/core/internal/Function.java new file mode 100644 index 0000000000..a9c4d3270b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/internal/Function.java @@ -0,0 +1,5 @@ +package org.springframework.cglib.core.internal; + +public interface Function { + V apply(K key); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java b/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java new file mode 100644 index 0000000000..4c63211683 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/internal/LoadingCache.java @@ -0,0 +1,87 @@ +package org.springframework.cglib.core.internal; + +import java.util.concurrent.*; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class LoadingCache { + protected final ConcurrentMap map; + protected final Function loader; + protected final Function keyMapper; + + public static final Function IDENTITY = new Function() { + public Object apply(Object key) { + return key; + } + }; + + public LoadingCache(Function keyMapper, Function loader) { + this.keyMapper = keyMapper; + this.loader = loader; + this.map = new ConcurrentHashMap(); + } + + @SuppressWarnings("unchecked") + public static Function identity() { + return IDENTITY; + } + + public V get(K key) { + final KK cacheKey = keyMapper.apply(key); + Object v = map.get(cacheKey); + if (v != null && !(v instanceof FutureTask)) { + return (V) v; + } + + return createEntry(key, cacheKey, v); + } + + /** + * Loads entry to the cache. + * If entry is missing, put {@link FutureTask} first so other competing thread might wait for the result. + * @param key original key that would be used to load the instance + * @param cacheKey key that would be used to store the entry in internal map + * @param v null or {@link FutureTask} + * @return newly created instance + */ + protected V createEntry(final K key, KK cacheKey, Object v) { + FutureTask task; + boolean creator = false; + if (v != null) { + // Another thread is already loading an instance + task = (FutureTask) v; + } else { + task = new FutureTask(new Callable() { + public V call() throws Exception { + return loader.apply(key); + } + }); + Object prevTask = map.putIfAbsent(cacheKey, task); + if (prevTask == null) { + // creator does the load + creator = true; + task.run(); + } else if (prevTask instanceof FutureTask) { + task = (FutureTask) prevTask; + } else { + return (V) prevTask; + } + } + + V result; + try { + result = task.get(); + } catch (InterruptedException e) { + throw new IllegalStateException("Interrupted while loading cache item", e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw ((RuntimeException) cause); + } + throw new IllegalStateException("Unable to load cache item", cause); + } + if (creator) { + map.put(cacheKey, result); + } + return result; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java new file mode 100644 index 0000000000..983379385c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/BridgeMethodResolver.java @@ -0,0 +1,127 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 + * + * https://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. + */ + +package org.springframework.cglib.proxy; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.Signature; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; + +/** + * Uses bytecode reflection to figure out the targets of all bridge methods that use invokespecial + * and invokeinterface, so that we can later rewrite them to use invokevirtual. + * + *

For interface bridges, using invokesuper will fail since the method being bridged to is in a + * superinterface, not a superclass. Starting in Java 8, javac emits default bridge methods in + * interfaces, which use invokeinterface to bridge to the target method. + * + * @author sberlin@gmail.com (Sam Berlin) + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +class BridgeMethodResolver { + + private final Map/* */declToBridge; + private final ClassLoader classLoader; + + public BridgeMethodResolver(Map declToBridge, ClassLoader classLoader) { + this.declToBridge = declToBridge; + this.classLoader = classLoader; + } + + /** + * Finds all bridge methods that are being called with invokespecial & + * returns them. + */ + public Map/**/resolveAll() { + Map resolved = new HashMap(); + for (Iterator entryIter = declToBridge.entrySet().iterator(); entryIter.hasNext(); ) { + Map.Entry entry = (Map.Entry) entryIter.next(); + Class owner = (Class) entry.getKey(); + Set bridges = (Set) entry.getValue(); + try { + InputStream is = classLoader.getResourceAsStream(owner.getName().replace('.', '/') + ".class"); + if (is == null) { + return resolved; + } + try { + new ClassReader(is) + .accept(new BridgedFinder(bridges, resolved), + ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + } finally { + is.close(); + } + } catch (IOException ignored) {} + } + return resolved; + } + + private static class BridgedFinder extends ClassVisitor { + private Map/**/ resolved; + private Set/**/ eligibleMethods; + + private Signature currentMethod = null; + + BridgedFinder(Set eligibleMethods, Map resolved) { + super(Constants.ASM_API); + this.resolved = resolved; + this.eligibleMethods = eligibleMethods; + } + + public void visit(int version, int access, String name, + String signature, String superName, String[] interfaces) { + } + + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + Signature sig = new Signature(name, desc); + if (eligibleMethods.remove(sig)) { + currentMethod = sig; + return new MethodVisitor(Constants.ASM_API) { + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if ((opcode == Opcodes.INVOKESPECIAL + || (itf && opcode == Opcodes.INVOKEINTERFACE)) + && currentMethod != null) { + Signature target = new Signature(name, desc); + // If the target signature is the same as the current, + // we shouldn't change our bridge becaues invokespecial + // is the only way to make progress (otherwise we'll + // get infinite recursion). This would typically + // only happen when a bridge method is created to widen + // the visibility of a superclass' method. + if (!target.equals(currentMethod)) { + resolved.put(currentMethod, target); + } + currentMethod = null; + } + } + }; + } else { + return null; + } + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Callback.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Callback.java new file mode 100644 index 0000000000..103cd7db99 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Callback.java @@ -0,0 +1,29 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * All callback interfaces used by {@link Enhancer} extend this interface. + * @see MethodInterceptor + * @see NoOp + * @see LazyLoader + * @see Dispatcher + * @see InvocationHandler + * @see FixedValue + */ +public interface Callback +{ +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java new file mode 100644 index 0000000000..1fc8079c51 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackFilter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.lang.reflect.Method; + +/** + * Map methods of subclasses generated by {@link Enhancer} to a particular + * callback. The type of the callbacks chosen for each method affects + * the bytecode generated for that method in the subclass, and cannot + * change for the life of the class. + *

Note: {@link CallbackFilter} implementations are supposed to be + * lightweight as cglib might keep {@link CallbackFilter} objects + * alive to enable caching of generated classes. Prefer using {@code static} + * classes for implementation of {@link CallbackFilter}.

+ */ +public interface CallbackFilter { + /** + * Map a method to a callback. + * @param method the intercepted method + * @return the index into the array of callbacks (as specified by {@link Enhancer#setCallbacks}) to use for the method, + */ + int accept(Method method); + + /** + * The CallbackFilter in use affects which cached class + * the Enhancer will use, so this is a reminder that + * you should correctly implement equals and + * hashCode for custom CallbackFilter + * implementations in order to improve performance. + */ + boolean equals(Object o); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java new file mode 100644 index 0000000000..168dab3b11 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackGenerator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.util.List; +import org.springframework.cglib.core.*; + +@SuppressWarnings({"rawtypes", "unchecked"}) +interface CallbackGenerator +{ + void generate(ClassEmitter ce, Context context, List methods) throws Exception; + void generateStatic(CodeEmitter e, Context context, List methods) throws Exception; + + interface Context + { + ClassLoader getClassLoader(); + CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method); + int getOriginalModifiers(MethodInfo method); + int getIndex(MethodInfo method); + void emitCallback(CodeEmitter ce, int index); + Signature getImplSignature(MethodInfo method); + void emitLoadArgsAndInvoke(CodeEmitter e, MethodInfo method); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java new file mode 100644 index 0000000000..9aa54f0728 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import org.springframework.cglib.core.ReflectUtils; +import java.lang.reflect.Method; +import java.util.*; + +/** + * @version $Id: CallbackHelper.java,v 1.2 2004/06/24 21:15:20 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class CallbackHelper +implements CallbackFilter +{ + private Map methodMap = new HashMap(); + private List callbacks = new ArrayList(); + + public CallbackHelper(Class superclass, Class[] interfaces) + { + List methods = new ArrayList(); + Enhancer.getMethods(superclass, interfaces, methods); + Map indexes = new HashMap(); + for (int i = 0, size = methods.size(); i < size; i++) { + Method method = (Method)methods.get(i); + Object callback = getCallback(method); + if (callback == null) + throw new IllegalStateException("getCallback cannot return null"); + boolean isCallback = callback instanceof Callback; + if (!(isCallback || (callback instanceof Class))) + throw new IllegalStateException("getCallback must return a Callback or a Class"); + if (i > 0 && ((callbacks.get(i - 1) instanceof Callback) ^ isCallback)) + throw new IllegalStateException("getCallback must return a Callback or a Class consistently for every Method"); + Integer index = (Integer)indexes.get(callback); + if (index == null) { + index = callbacks.size(); + indexes.put(callback, index); + } + methodMap.put(method, index); + callbacks.add(callback); + } + } + + abstract protected Object getCallback(Method method); + + public Callback[] getCallbacks() + { + if (callbacks.size() == 0) + return new Callback[0]; + if (callbacks.get(0) instanceof Callback) { + return (Callback[])callbacks.toArray(new Callback[callbacks.size()]); + } else { + throw new IllegalStateException("getCallback returned classes, not callbacks; call getCallbackTypes instead"); + } + } + + public Class[] getCallbackTypes() + { + if (callbacks.size() == 0) + return new Class[0]; + if (callbacks.get(0) instanceof Callback) { + return ReflectUtils.getClasses(getCallbacks()); + } else { + return (Class[])callbacks.toArray(new Class[callbacks.size()]); + } + } + + public int accept(Method method) + { + return ((Integer)methodMap.get(method)).intValue(); + } + + public int hashCode() + { + return methodMap.hashCode(); + } + + public boolean equals(Object o) + { + if (o == null) + return false; + if (!(o instanceof CallbackHelper)) + return false; + return methodMap.equals(((CallbackHelper)o).methodMap); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java new file mode 100644 index 0000000000..e1a5cbc5bb --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/CallbackInfo.java @@ -0,0 +1,117 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +class CallbackInfo +{ + public static Type[] determineTypes(Class[] callbackTypes) { + return determineTypes(callbackTypes, true); + } + + public static Type[] determineTypes(Class[] callbackTypes, boolean checkAll) { + Type[] types = new Type[callbackTypes.length]; + for (int i = 0; i < types.length; i++) { + types[i] = determineType(callbackTypes[i], checkAll); + } + return types; + } + + public static Type[] determineTypes(Callback[] callbacks) { + return determineTypes(callbacks, true); + } + + public static Type[] determineTypes(Callback[] callbacks, boolean checkAll) { + Type[] types = new Type[callbacks.length]; + for (int i = 0; i < types.length; i++) { + types[i] = determineType(callbacks[i], checkAll); + } + return types; + } + + public static CallbackGenerator[] getGenerators(Type[] callbackTypes) { + CallbackGenerator[] generators = new CallbackGenerator[callbackTypes.length]; + for (int i = 0; i < generators.length; i++) { + generators[i] = getGenerator(callbackTypes[i]); + } + return generators; + } + + //////////////////// PRIVATE //////////////////// + + private Class cls; + private CallbackGenerator generator; + private Type type; + + private static final CallbackInfo[] CALLBACKS = { + new CallbackInfo(NoOp.class, NoOpGenerator.INSTANCE), + new CallbackInfo(MethodInterceptor.class, MethodInterceptorGenerator.INSTANCE), + new CallbackInfo(InvocationHandler.class, InvocationHandlerGenerator.INSTANCE), + new CallbackInfo(LazyLoader.class, LazyLoaderGenerator.INSTANCE), + new CallbackInfo(Dispatcher.class, DispatcherGenerator.INSTANCE), + new CallbackInfo(FixedValue.class, FixedValueGenerator.INSTANCE), + new CallbackInfo(ProxyRefDispatcher.class, DispatcherGenerator.PROXY_REF_INSTANCE), + }; + + private CallbackInfo(Class cls, CallbackGenerator generator) { + this.cls = cls; + this.generator = generator; + type = Type.getType(cls); + } + + private static Type determineType(Callback callback, boolean checkAll) { + if (callback == null) { + throw new IllegalStateException("Callback is null"); + } + return determineType(callback.getClass(), checkAll); + } + + private static Type determineType(Class callbackType, boolean checkAll) { + Class cur = null; + Type type = null; + for (int i = 0; i < CALLBACKS.length; i++) { + CallbackInfo info = CALLBACKS[i]; + if (info.cls.isAssignableFrom(callbackType)) { + if (cur != null) { + throw new IllegalStateException("Callback implements both " + cur + " and " + info.cls); + } + cur = info.cls; + type = info.type; + if (!checkAll) { + break; + } + } + } + if (cur == null) { + throw new IllegalStateException("Unknown callback type " + callbackType); + } + return type; + } + + private static CallbackGenerator getGenerator(Type callbackType) { + for (int i = 0; i < CALLBACKS.length; i++) { + CallbackInfo info = CALLBACKS[i]; + if (info.type.equals(callbackType)) { + return info.generator; + } + } + throw new IllegalStateException("Unknown callback type " + callbackType); + } +} + + diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Dispatcher.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Dispatcher.java new file mode 100644 index 0000000000..c090f0cc44 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Dispatcher.java @@ -0,0 +1,30 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * Dispatching {@link Enhancer} callback. This is identical to the + * {@link LazyLoader} interface but needs to be separate so that Enhancer + * knows which type of code to generate. + */ +public interface Dispatcher extends Callback { + /** + * Return the object which the original method invocation should + * be dispatched. This method is called for every method invocation. + * @return an object that can invoke the method + */ + Object loadObject() throws Exception; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java new file mode 100644 index 0000000000..154f7d7363 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/DispatcherGenerator.java @@ -0,0 +1,66 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +class DispatcherGenerator implements CallbackGenerator { + public static final DispatcherGenerator INSTANCE = + new DispatcherGenerator(false); + public static final DispatcherGenerator PROXY_REF_INSTANCE = + new DispatcherGenerator(true); + + private static final Type DISPATCHER = + TypeUtils.parseType("org.springframework.cglib.proxy.Dispatcher"); + private static final Type PROXY_REF_DISPATCHER = + TypeUtils.parseType("org.springframework.cglib.proxy.ProxyRefDispatcher"); + private static final Signature LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject()"); + private static final Signature PROXY_REF_LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject(Object)"); + + private boolean proxyRef; + + private DispatcherGenerator(boolean proxyRef) { + this.proxyRef = proxyRef; + } + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + if (!TypeUtils.isProtected(method.getModifiers())) { + CodeEmitter e = context.beginMethod(ce, method); + context.emitCallback(e, context.getIndex(method)); + if (proxyRef) { + e.load_this(); + e.invoke_interface(PROXY_REF_DISPATCHER, PROXY_REF_LOAD_OBJECT); + } else { + e.invoke_interface(DISPATCHER, LOAD_OBJECT); + } + e.checkcast(method.getClassInfo().getType()); + e.load_args(); + e.invoke(method); + e.return_value(); + e.end_method(); + } + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java new file mode 100644 index 0000000000..c924bb1048 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Factory.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002,2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ + +package org.springframework.cglib.proxy; + +/** + * All enhanced instances returned by the {@link Enhancer} class implement this interface. + * Using this interface for new instances is faster than going through the Enhancer + * interface or using reflection. In addition, to intercept methods called during + * object construction you must use these methods instead of reflection. + * @author Juozas Baliuka baliuka@mwm.lt + * @version $Id: Factory.java,v 1.13 2004/06/24 21:15:20 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public interface Factory { + /** + * Creates new instance of the same type, using the no-arg constructor. + * The class of this object must have been created using a single Callback type. + * If multiple callbacks are required an exception will be thrown. + * @param callback the new interceptor to use + * @return new instance of the same type + */ + Object newInstance(Callback callback); + + /** + * Creates new instance of the same type, using the no-arg constructor. + * @param callbacks the new callbacks(s) to use + * @return new instance of the same type + */ + Object newInstance(Callback[] callbacks); + + /** + * Creates a new instance of the same type, using the constructor + * matching the given signature. + * @param types the constructor argument types + * @param args the constructor arguments + * @param callbacks the new interceptor(s) to use + * @return new instance of the same type + */ + Object newInstance(Class[] types, Object[] args, Callback[] callbacks); + + /** + * Return the Callback implementation at the specified index. + * @param index the callback index + * @return the callback implementation + */ + Callback getCallback(int index); + + /** + * Set the callback for this object for the given type. + * @param index the callback index to replace + * @param callback the new callback + */ + void setCallback(int index, Callback callback); + + /** + * Replace all of the callbacks for this object at once. + * @param callbacks the new callbacks(s) to use + */ + void setCallbacks(Callback[] callbacks); + + /** + * Get the current set of callbacks for ths object. + * @return a new array instance + */ + Callback[] getCallbacks(); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValue.java b/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValue.java new file mode 100644 index 0000000000..6e13a1f79f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValue.java @@ -0,0 +1,35 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * {@link Enhancer} callback that simply returns the value to return + * from the proxied method. No information about what method + * is being called is available to the callback, and the type of + * the returned object must be compatible with the return type of + * the proxied method. This makes this callback primarily useful + * for forcing a particular method (through the use of a {@link CallbackFilter} + * to return a fixed value with little overhead. + */ +public interface FixedValue extends Callback { + /** + * Return the object which the original method invocation should + * return. This method is called for every method invocation. + * @return an object matching the type of the return value for every + * method this callback is mapped to + */ + Object loadObject() throws Exception; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java new file mode 100644 index 0000000000..f34e2b4689 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/FixedValueGenerator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +class FixedValueGenerator implements CallbackGenerator { + public static final FixedValueGenerator INSTANCE = new FixedValueGenerator(); + private static final Type FIXED_VALUE = + TypeUtils.parseType("org.springframework.cglib.proxy.FixedValue"); + private static final Signature LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject()"); + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + CodeEmitter e = context.beginMethod(ce, method); + context.emitCallback(e, context.getIndex(method)); + e.invoke_interface(FIXED_VALUE, LOAD_OBJECT); + e.unbox_or_zero(e.getReturnType()); + e.return_value(); + e.end_method(); + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java b/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java new file mode 100644 index 0000000000..258412c592 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/InterfaceMaker.java @@ -0,0 +1,119 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.lang.reflect.*; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Type; + +/** + * Generates new interfaces at runtime. + * By passing a generated interface to the Enhancer's list of interfaces to + * implement, you can make your enhanced classes handle an arbitrary set + * of method signatures. + * @author Chris Nokleberg + * @version $Id: InterfaceMaker.java,v 1.4 2006/03/05 02:43:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class InterfaceMaker extends AbstractClassGenerator +{ + private static final Source SOURCE = new Source(InterfaceMaker.class.getName()); + private Map signatures = new HashMap(); + + /** + * Create a new InterfaceMaker. A new InterfaceMaker + * object should be used for each generated interface, and should not + * be shared across threads. + */ + public InterfaceMaker() { + super(SOURCE); + } + + /** + * Add a method signature to the interface. + * @param sig the method signature to add to the interface + * @param exceptions an array of exception types to declare for the method + */ + public void add(Signature sig, Type[] exceptions) { + signatures.put(sig, exceptions); + } + + /** + * Add a method signature to the interface. The method modifiers are ignored, + * since interface methods are by definition abstract and public. + * @param method the method to add to the interface + */ + public void add(Method method) { + add(ReflectUtils.getSignature(method), + ReflectUtils.getExceptionTypes(method)); + } + + /** + * Add all the public methods in the specified class. + * Methods from superclasses are included, except for methods declared in the base + * Object class (e.g. getClass, equals, hashCode). + * @param class the class containing the methods to add to the interface + */ + public void add(Class clazz) { + Method[] methods = clazz.getMethods(); + for (int i = 0; i < methods.length; i++) { + Method m = methods[i]; + if (!m.getDeclaringClass().getName().equals("java.lang.Object")) { + add(m); + } + } + } + + /** + * Create an interface using the current set of method signatures. + */ + public Class create() { + setUseCache(false); + return (Class)super.create(this); + } + + protected ClassLoader getDefaultClassLoader() { + return null; + } + + protected Object firstInstance(Class type) { + return type; + } + + protected Object nextInstance(Object instance) { + throw new IllegalStateException("InterfaceMaker does not cache"); + } + + public void generateClass(ClassVisitor v) throws Exception { + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_8, + Constants.ACC_PUBLIC | Constants.ACC_INTERFACE | Constants.ACC_ABSTRACT, + getClassName(), + null, + null, + Constants.SOURCE_FILE); + for (Iterator it = signatures.keySet().iterator(); it.hasNext();) { + Signature sig = (Signature)it.next(); + Type[] exceptions = (Type[])signatures.get(sig); + ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT, + sig, + exceptions).end_method(); + } + ce.end_class(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandler.java b/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandler.java new file mode 100644 index 0000000000..cf0a35477f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.lang.reflect.Method; + +/** + * {@link java.lang.reflect.InvocationHandler} replacement (unavailable under JDK 1.2). + * This callback type is primarily for use by the {@link Proxy} class but + * may be used with {@link Enhancer} as well. + * @author Neeme Praks neeme@apache.org + * @version $Id: InvocationHandler.java,v 1.3 2004/06/24 21:15:20 herbyderby Exp $ + */ +public interface InvocationHandler +extends Callback +{ + /** + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object) + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java new file mode 100644 index 0000000000..d788729a56 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/InvocationHandlerGenerator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import org.springframework.cglib.core.*; +import java.util.*; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +class InvocationHandlerGenerator +implements CallbackGenerator +{ + public static final InvocationHandlerGenerator INSTANCE = new InvocationHandlerGenerator(); + + private static final Type INVOCATION_HANDLER = + TypeUtils.parseType("org.springframework.cglib.proxy.InvocationHandler"); + private static final Type UNDECLARED_THROWABLE_EXCEPTION = + TypeUtils.parseType("org.springframework.cglib.proxy.UndeclaredThrowableException"); + private static final Type METHOD = + TypeUtils.parseType("java.lang.reflect.Method"); + private static final Signature INVOKE = + TypeUtils.parseSignature("Object invoke(Object, java.lang.reflect.Method, Object[])"); + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + Signature impl = context.getImplSignature(method); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, impl.getName(), METHOD, null); + + CodeEmitter e = context.beginMethod(ce, method); + Block handler = e.begin_block(); + context.emitCallback(e, context.getIndex(method)); + e.load_this(); + e.getfield(impl.getName()); + e.create_arg_array(); + e.invoke_interface(INVOCATION_HANDLER, INVOKE); + e.unbox(method.getSignature().getReturnType()); + e.return_value(); + handler.end(); + EmitUtils.wrap_undeclared_throwable(e, handler, method.getExceptionTypes(), UNDECLARED_THROWABLE_EXCEPTION); + e.end_method(); + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + EmitUtils.load_method(e, method); + e.putfield(context.getImplSignature(method).getName()); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoader.java b/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoader.java new file mode 100644 index 0000000000..2d1f35ca7c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoader.java @@ -0,0 +1,30 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * Lazy-loading {@link Enhancer} callback. + */ +public interface LazyLoader extends Callback { + /** + * Return the object which the original method invocation should be + * dispatched. Called as soon as the first lazily-loaded method in + * the enhanced instance is invoked. The same object is then used + * for every future method call to the proxy instance. + * @return an object that can invoke the method + */ + Object loadObject() throws Exception; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java new file mode 100644 index 0000000000..49261578b4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/LazyLoaderGenerator.java @@ -0,0 +1,89 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +class LazyLoaderGenerator implements CallbackGenerator { + public static final LazyLoaderGenerator INSTANCE = new LazyLoaderGenerator(); + + private static final Signature LOAD_OBJECT = + TypeUtils.parseSignature("Object loadObject()"); + private static final Type LAZY_LOADER = + TypeUtils.parseType("org.springframework.cglib.proxy.LazyLoader"); + + public void generate(ClassEmitter ce, Context context, List methods) { + Set indexes = new HashSet(); + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + if (TypeUtils.isProtected(method.getModifiers())) { + // ignore protected methods + } else { + int index = context.getIndex(method); + indexes.add(index); + CodeEmitter e = context.beginMethod(ce, method); + e.load_this(); + e.dup(); + e.invoke_virtual_this(loadMethod(index)); + e.checkcast(method.getClassInfo().getType()); + e.load_args(); + e.invoke(method); + e.return_value(); + e.end_method(); + } + } + + for (Iterator it = indexes.iterator(); it.hasNext();) { + int index = ((Integer)it.next()); + + String delegate = "CGLIB$LAZY_LOADER_" + index; + ce.declare_field(Constants.ACC_PRIVATE, delegate, Constants.TYPE_OBJECT, null); + + CodeEmitter e = ce.begin_method(Constants.ACC_PRIVATE | + Constants.ACC_SYNCHRONIZED | + Constants.ACC_FINAL, + loadMethod(index), + null); + e.load_this(); + e.getfield(delegate); + e.dup(); + Label end = e.make_label(); + e.ifnonnull(end); + e.pop(); + e.load_this(); + context.emitCallback(e, index); + e.invoke_interface(LAZY_LOADER, LOAD_OBJECT); + e.dup_x1(); + e.putfield(delegate); + e.mark(end); + e.return_value(); + e.end_method(); + + } + } + + private Signature loadMethod(int index) { + return new Signature("CGLIB$LOAD_PRIVATE_" + index, + Constants.TYPE_OBJECT, + Constants.TYPES_EMPTY); + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java new file mode 100644 index 0000000000..741ef943b1 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptor.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002,2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * General-purpose {@link Enhancer} callback which provides for "around advice". + * @author Juozas Baliuka baliuka@mwm.lt + * @version $Id: MethodInterceptor.java,v 1.8 2004/06/24 21:15:20 herbyderby Exp $ + */ +public interface MethodInterceptor +extends Callback +{ + /** + * All generated proxied methods call this method instead of the original method. + * The original method may either be invoked by normal reflection using the Method object, + * or by using the MethodProxy (faster). + * @param obj "this", the enhanced object + * @param method intercepted Method + * @param args argument array; primitive types are wrapped + * @param proxy used to invoke super (non-intercepted method); may be called + * as many times as needed + * @throws Throwable any exception may be thrown; if so, super method will not be invoked + * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value. + * @see MethodProxy + */ + public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, + MethodProxy proxy) throws Throwable; + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java new file mode 100644 index 0000000000..d944e96dce --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodInterceptorGenerator.java @@ -0,0 +1,238 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) +class MethodInterceptorGenerator +implements CallbackGenerator +{ + public static final MethodInterceptorGenerator INSTANCE = new MethodInterceptorGenerator(); + + static final String EMPTY_ARGS_NAME = "CGLIB$emptyArgs"; + static final String FIND_PROXY_NAME = "CGLIB$findMethodProxy"; + static final Class[] FIND_PROXY_TYPES = { Signature.class }; + + private static final Type ABSTRACT_METHOD_ERROR = + TypeUtils.parseType("AbstractMethodError"); + private static final Type METHOD = + TypeUtils.parseType("java.lang.reflect.Method"); + private static final Type REFLECT_UTILS = + TypeUtils.parseType("org.springframework.cglib.core.ReflectUtils"); + private static final Type METHOD_PROXY = + TypeUtils.parseType("org.springframework.cglib.proxy.MethodProxy"); + private static final Type METHOD_INTERCEPTOR = + TypeUtils.parseType("org.springframework.cglib.proxy.MethodInterceptor"); + private static final Signature GET_DECLARED_METHODS = + TypeUtils.parseSignature("java.lang.reflect.Method[] getDeclaredMethods()"); + private static final Signature GET_DECLARING_CLASS = + TypeUtils.parseSignature("Class getDeclaringClass()"); + private static final Signature FIND_METHODS = + TypeUtils.parseSignature("java.lang.reflect.Method[] findMethods(String[], java.lang.reflect.Method[])"); + private static final Signature MAKE_PROXY = + new Signature("create", METHOD_PROXY, new Type[]{ + Constants.TYPE_CLASS, + Constants.TYPE_CLASS, + Constants.TYPE_STRING, + Constants.TYPE_STRING, + Constants.TYPE_STRING + }); + private static final Signature INTERCEPT = + new Signature("intercept", Constants.TYPE_OBJECT, new Type[]{ + Constants.TYPE_OBJECT, + METHOD, + Constants.TYPE_OBJECT_ARRAY, + METHOD_PROXY + }); + private static final Signature FIND_PROXY = + new Signature(FIND_PROXY_NAME, METHOD_PROXY, new Type[]{ Constants.TYPE_SIGNATURE }); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Transformer METHOD_TO_CLASS = new Transformer(){ + public Object transform(Object value) { + return ((MethodInfo)value).getClassInfo(); + } + }; + private static final Signature CSTRUCT_SIGNATURE = + TypeUtils.parseConstructor("String, String"); + + private String getMethodField(Signature impl) { + return impl.getName() + "$Method"; + } + private String getMethodProxyField(Signature impl) { + return impl.getName() + "$Proxy"; + } + + public void generate(ClassEmitter ce, Context context, List methods) { + Map sigMap = new HashMap(); + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + Signature sig = method.getSignature(); + Signature impl = context.getImplSignature(method); + + String methodField = getMethodField(impl); + String methodProxyField = getMethodProxyField(impl); + + sigMap.put(sig.toString(), methodProxyField); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, methodField, METHOD, null); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, methodProxyField, METHOD_PROXY, null); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, EMPTY_ARGS_NAME, Constants.TYPE_OBJECT_ARRAY, null); + CodeEmitter e; + + // access method + e = ce.begin_method(Constants.ACC_FINAL, + impl, + method.getExceptionTypes()); + superHelper(e, method, context); + e.return_value(); + e.end_method(); + + // around method + e = context.beginMethod(ce, method); + Label nullInterceptor = e.make_label(); + context.emitCallback(e, context.getIndex(method)); + e.dup(); + e.ifnull(nullInterceptor); + + e.load_this(); + e.getfield(methodField); + + if (sig.getArgumentTypes().length == 0) { + e.getfield(EMPTY_ARGS_NAME); + } else { + e.create_arg_array(); + } + + e.getfield(methodProxyField); + e.invoke_interface(METHOD_INTERCEPTOR, INTERCEPT); + e.unbox_or_zero(sig.getReturnType()); + e.return_value(); + + e.mark(nullInterceptor); + superHelper(e, method, context); + e.return_value(); + e.end_method(); + } + generateFindProxy(ce, sigMap); + } + + private static void superHelper(CodeEmitter e, MethodInfo method, Context context) + { + if (TypeUtils.isAbstract(method.getModifiers())) { + e.throw_exception(ABSTRACT_METHOD_ERROR, method.toString() + " is abstract" ); + } else { + e.load_this(); + context.emitLoadArgsAndInvoke(e, method); + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) throws Exception { + /* generates: + static { + Class thisClass = Class.forName("NameOfThisClass"); + Class cls = Class.forName("java.lang.Object"); + String[] sigs = new String[]{ "toString", "()Ljava/lang/String;", ... }; + Method[] methods = cls.getDeclaredMethods(); + methods = ReflectUtils.findMethods(sigs, methods); + METHOD_0 = methods[0]; + CGLIB$ACCESS_0 = MethodProxy.create(cls, thisClass, "()Ljava/lang/String;", "toString", "CGLIB$ACCESS_0"); + ... + } + */ + + e.push(0); + e.newarray(); + e.putfield(EMPTY_ARGS_NAME); + + Local thisclass = e.make_local(); + Local declaringclass = e.make_local(); + EmitUtils.load_class_this(e); + e.store_local(thisclass); + + Map methodsByClass = CollectionUtils.bucket(methods, METHOD_TO_CLASS); + for (Iterator i = methodsByClass.keySet().iterator(); i.hasNext();) { + ClassInfo classInfo = (ClassInfo)i.next(); + + List classMethods = (List)methodsByClass.get(classInfo); + e.push(2 * classMethods.size()); + e.newarray(Constants.TYPE_STRING); + for (int index = 0; index < classMethods.size(); index++) { + MethodInfo method = (MethodInfo)classMethods.get(index); + Signature sig = method.getSignature(); + e.dup(); + e.push(2 * index); + e.push(sig.getName()); + e.aastore(); + e.dup(); + e.push(2 * index + 1); + e.push(sig.getDescriptor()); + e.aastore(); + } + + EmitUtils.load_class(e, classInfo.getType()); + e.dup(); + e.store_local(declaringclass); + e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHODS); + e.invoke_static(REFLECT_UTILS, FIND_METHODS); + + for (int index = 0; index < classMethods.size(); index++) { + MethodInfo method = (MethodInfo)classMethods.get(index); + Signature sig = method.getSignature(); + Signature impl = context.getImplSignature(method); + e.dup(); + e.push(index); + e.array_load(METHOD); + e.putfield(getMethodField(impl)); + + e.load_local(declaringclass); + e.load_local(thisclass); + e.push(sig.getDescriptor()); + e.push(sig.getName()); + e.push(impl.getName()); + e.invoke_static(METHOD_PROXY, MAKE_PROXY); + e.putfield(getMethodProxyField(impl)); + } + e.pop(); + } + } + + public void generateFindProxy(ClassEmitter ce, final Map sigMap) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC, + FIND_PROXY, + null); + e.load_arg(0); + e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); + ObjectSwitchCallback callback = new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + e.getfield((String)sigMap.get(key)); + e.return_value(); + } + public void processDefault() { + e.aconst_null(); + e.return_value(); + } + }; + EmitUtils.string_switch(e, + (String[])sigMap.keySet().toArray(new String[0]), + Constants.SWITCH_STYLE_HASH, + callback); + e.end_method(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Mixin.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Mixin.java new file mode 100644 index 0000000000..1e06c8ccf2 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Mixin.java @@ -0,0 +1,243 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.security.ProtectionDomain; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; + + + +/** + * Mixin allows + * multiple objects to be combined into a single larger object. The + * methods in the generated object simply call the original methods in the + * underlying "delegate" objects. + * @author Chris Nokleberg + * @version $Id: Mixin.java,v 1.7 2005/09/27 11:42:27 baliuka Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class Mixin { + private static final MixinKey KEY_FACTORY = + (MixinKey)KeyFactory.create(MixinKey.class, KeyFactory.CLASS_BY_NAME); + private static final Map ROUTE_CACHE = Collections.synchronizedMap(new HashMap()); + + public static final int STYLE_INTERFACES = 0; + public static final int STYLE_BEANS = 1; + public static final int STYLE_EVERYTHING = 2; + + interface MixinKey { + public Object newInstance(int style, String[] classes, int[] route); + } + + abstract public Mixin newInstance(Object[] delegates); + + /** + * Helper method to create an interface mixin. For finer control over the + * generated instance, use a new instance of Mixin + * instead of this static method. + * TODO + */ + public static Mixin create(Object[] delegates) { + Generator gen = new Generator(); + gen.setDelegates(delegates); + return gen.create(); + } + + /** + * Helper method to create an interface mixin. For finer control over the + * generated instance, use a new instance of Mixin + * instead of this static method. + * TODO + */ + public static Mixin create(Class[] interfaces, Object[] delegates) { + Generator gen = new Generator(); + gen.setClasses(interfaces); + gen.setDelegates(delegates); + return gen.create(); + } + + + public static Mixin createBean(Object[] beans) { + + return createBean(null, beans); + + } + /** + * Helper method to create a bean mixin. For finer control over the + * generated instance, use a new instance of Mixin + * instead of this static method. + * TODO + */ + public static Mixin createBean(ClassLoader loader,Object[] beans) { + Generator gen = new Generator(); + gen.setStyle(STYLE_BEANS); + gen.setDelegates(beans); + gen.setClassLoader(loader); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(Mixin.class.getName()); + + private Class[] classes; + private Object[] delegates; + private int style = STYLE_INTERFACES; + + private int[] route; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return classes[0].getClassLoader(); // is this right? + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(classes[0]); + } + + public void setStyle(int style) { + switch (style) { + case STYLE_INTERFACES: + case STYLE_BEANS: + case STYLE_EVERYTHING: + this.style = style; + break; + default: + throw new IllegalArgumentException("Unknown mixin style: " + style); + } + } + + public void setClasses(Class[] classes) { + this.classes = classes; + } + + public void setDelegates(Object[] delegates) { + this.delegates = delegates; + } + + public Mixin create() { + if (classes == null && delegates == null) { + throw new IllegalStateException("Either classes or delegates must be set"); + } + switch (style) { + case STYLE_INTERFACES: + if (classes == null) { + Route r = route(delegates); + classes = r.classes; + route = r.route; + } + break; + case STYLE_BEANS: + // fall-through + case STYLE_EVERYTHING: + if (classes == null) { + classes = ReflectUtils.getClasses(delegates); + } else { + if (delegates != null) { + Class[] temp = ReflectUtils.getClasses(delegates); + if (classes.length != temp.length) { + throw new IllegalStateException("Specified classes are incompatible with delegates"); + } + for (int i = 0; i < classes.length; i++) { + if (!classes[i].isAssignableFrom(temp[i])) { + throw new IllegalStateException("Specified class " + classes[i] + " is incompatible with delegate class " + temp[i] + " (index " + i + ")"); + } + } + } + } + } + setNamePrefix(classes[ReflectUtils.findPackageProtected(classes)].getName()); + + return (Mixin)super.create(KEY_FACTORY.newInstance(style, ReflectUtils.getNames( classes ), route)); + } + + public void generateClass(ClassVisitor v) { + switch (style) { + case STYLE_INTERFACES: + new MixinEmitter(v, getClassName(), classes, route); + break; + case STYLE_BEANS: + new MixinBeanEmitter(v, getClassName(), classes); + break; + case STYLE_EVERYTHING: + new MixinEverythingEmitter(v, getClassName(), classes); + break; + } + } + + protected Object firstInstance(Class type) { + return ((Mixin)ReflectUtils.newInstance(type)).newInstance(delegates); + } + + protected Object nextInstance(Object instance) { + return ((Mixin)instance).newInstance(delegates); + } + } + + public static Class[] getClasses(Object[] delegates) { + return route(delegates).classes.clone(); + } + +// public static int[] getRoute(Object[] delegates) { +// return (int[])route(delegates).route.clone(); +// } + + private static Route route(Object[] delegates) { + Object key = ClassesKey.create(delegates); + Route route = (Route)ROUTE_CACHE.get(key); + if (route == null) { + ROUTE_CACHE.put(key, route = new Route(delegates)); + } + return route; + } + + private static class Route + { + private Class[] classes; + private int[] route; + + Route(Object[] delegates) { + Map map = new HashMap(); + ArrayList collect = new ArrayList(); + for (int i = 0; i < delegates.length; i++) { + Class delegate = delegates[i].getClass(); + collect.clear(); + ReflectUtils.addAllInterfaces(delegate, collect); + for (Iterator it = collect.iterator(); it.hasNext();) { + Class iface = (Class)it.next(); + if (!map.containsKey(iface)) { + map.put(iface, i); + } + } + } + classes = new Class[map.size()]; + route = new int[map.size()]; + int index = 0; + for (Iterator it = map.keySet().iterator(); it.hasNext();) { + Class key = (Class)it.next(); + classes[index] = key; + route[index] = ((Integer)map.get(key)); + index++; + } + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java new file mode 100644 index 0000000000..865a67e518 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinBeanEmitter.java @@ -0,0 +1,39 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.lang.reflect.Method; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.asm.ClassVisitor; + +/** + * @author Chris Nokleberg + * @version $Id: MixinBeanEmitter.java,v 1.2 2004/06/24 21:15:20 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +class MixinBeanEmitter extends MixinEmitter { + public MixinBeanEmitter(ClassVisitor v, String className, Class[] classes) { + super(v, className, classes, null); + } + + protected Class[] getInterfaces(Class[] classes) { + return null; + } + + protected Method[] getMethods(Class type) { + return ReflectUtils.getPropertyMethods(ReflectUtils.getBeanProperties(type), true, true); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java new file mode 100644 index 0000000000..9fda5dac12 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEmitter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.lang.reflect.Method; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Type; + +/** + * @author Chris Nokleberg + * @version $Id: MixinEmitter.java,v 1.9 2006/08/27 21:04:37 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +class MixinEmitter extends ClassEmitter { + private static final String FIELD_NAME = "CGLIB$DELEGATES"; + private static final Signature CSTRUCT_OBJECT_ARRAY = + TypeUtils.parseConstructor("Object[]"); + private static final Type MIXIN = + TypeUtils.parseType("org.springframework.cglib.proxy.Mixin"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", MIXIN, new Type[]{ Constants.TYPE_OBJECT_ARRAY }); + + public MixinEmitter(ClassVisitor v, String className, Class[] classes, int[] route) { + super(v); + + begin_class(Constants.V1_8, + Constants.ACC_PUBLIC, + className, + MIXIN, + TypeUtils.getTypes(getInterfaces(classes)), + Constants.SOURCE_FILE); + EmitUtils.null_constructor(this); + EmitUtils.factory_method(this, NEW_INSTANCE); + + declare_field(Constants.ACC_PRIVATE, FIELD_NAME, Constants.TYPE_OBJECT_ARRAY, null); + + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT_ARRAY, null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + e.load_arg(0); + e.putfield(FIELD_NAME); + e.return_value(); + e.end_method(); + + Set unique = new HashSet(); + for (int i = 0; i < classes.length; i++) { + Method[] methods = getMethods(classes[i]); + for (int j = 0; j < methods.length; j++) { + if (unique.add(MethodWrapper.create(methods[j]))) { + MethodInfo method = ReflectUtils.getMethodInfo(methods[j]); + int modifiers = Constants.ACC_PUBLIC; + if ((method.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + modifiers |= Constants.ACC_VARARGS; + } + e = EmitUtils.begin_method(this, method, modifiers); + e.load_this(); + e.getfield(FIELD_NAME); + e.aaload((route != null) ? route[i] : i); + e.checkcast(method.getClassInfo().getType()); + e.load_args(); + e.invoke(method); + e.return_value(); + e.end_method(); + } + } + } + + end_class(); + } + + protected Class[] getInterfaces(Class[] classes) { + return classes; + } + + protected Method[] getMethods(Class type) { + return type.getMethods(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java new file mode 100644 index 0000000000..e335e218c1 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MixinEverythingEmitter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import org.springframework.cglib.core.CollectionUtils; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.RejectModifierPredicate; +import org.springframework.asm.ClassVisitor; + +/** + * @author Chris Nokleberg + * @version $Id: MixinEverythingEmitter.java,v 1.3 2004/06/24 21:15:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +class MixinEverythingEmitter extends MixinEmitter { + + public MixinEverythingEmitter(ClassVisitor v, String className, Class[] classes) { + super(v, className, classes, null); + } + + protected Class[] getInterfaces(Class[] classes) { + List list = new ArrayList(); + for (int i = 0; i < classes.length; i++) { + ReflectUtils.addAllInterfaces(classes[i], list); + } + return (Class[])list.toArray(new Class[list.size()]); + } + + protected Method[] getMethods(Class type) { + List methods = new ArrayList(Arrays.asList(type.getMethods())); + CollectionUtils.filter(methods, new RejectModifierPredicate(Modifier.FINAL | Modifier.STATIC)); + return (Method[])methods.toArray(new Method[methods.size()]); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/NoOp.java b/spring-core/src/main/java/org/springframework/cglib/proxy/NoOp.java new file mode 100644 index 0000000000..016d6bad37 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/NoOp.java @@ -0,0 +1,28 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * Methods using this {@link Enhancer} callback will delegate directly to the + * default (super) implementation in the base class. + */ +public interface NoOp extends Callback +{ + /** + * A thread-safe singleton instance of the NoOp callback. + */ + public static final NoOp INSTANCE = new NoOp() { }; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java b/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java new file mode 100644 index 0000000000..c2b6bd3bb1 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/NoOpGenerator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.util.Iterator; +import java.util.List; +import org.springframework.cglib.core.*; + +@SuppressWarnings({"rawtypes", "unchecked"}) +class NoOpGenerator +implements CallbackGenerator +{ + public static final NoOpGenerator INSTANCE = new NoOpGenerator(); + + public void generate(ClassEmitter ce, Context context, List methods) { + for (Iterator it = methods.iterator(); it.hasNext();) { + MethodInfo method = (MethodInfo)it.next(); + if (TypeUtils.isBridge(method.getModifiers()) || ( + TypeUtils.isProtected(context.getOriginalModifiers(method)) && + TypeUtils.isPublic(method.getModifiers()))) { + CodeEmitter e = EmitUtils.begin_method(ce, method); + e.load_this(); + context.emitLoadArgsAndInvoke(e, method); + e.return_value(); + e.end_method(); + } + } + } + + public void generateStatic(CodeEmitter e, Context context, List methods) { } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java new file mode 100644 index 0000000000..359c6483e2 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Proxy.java @@ -0,0 +1,102 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Member; +import org.springframework.cglib.core.CodeGenerationException; + +/** + * This class is meant to be used as replacement for + * java.lang.reflect.Proxy under JDK 1.2. There are some known + * subtle differences: + *
    + *
  • The exceptions returned by invoking getExceptionTypes + * on the Method passed to the invoke method + * are the exact set that can be thrown without resulting in an + * UndeclaredThrowableException being thrown. + *
  • {@link UndeclaredThrowableException} is used instead + * of java.lang.reflect.UndeclaredThrowableException. + *
+ *

+ * @version $Id: Proxy.java,v 1.6 2004/06/24 21:15:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked", "serial"}) +public class Proxy implements Serializable { + protected InvocationHandler h; + + private static final CallbackFilter BAD_OBJECT_METHOD_FILTER = new CallbackFilter() { + public int accept(Method method) { + if (method.getDeclaringClass().getName().equals("java.lang.Object")) { + String name = method.getName(); + if (!(name.equals("hashCode") || + name.equals("equals") || + name.equals("toString"))) { + return 1; + } + } + return 0; + } + }; + + protected Proxy(InvocationHandler h) { + Enhancer.registerCallbacks(getClass(), new Callback[]{ h, null }); + this.h = h; + } + + // private for security of isProxyClass + private static class ProxyImpl extends Proxy { + protected ProxyImpl(InvocationHandler h) { + super(h); + } + } + + public static InvocationHandler getInvocationHandler(Object proxy) { + if (!(proxy instanceof ProxyImpl)) { + throw new IllegalArgumentException("Object is not a proxy"); + } + return ((Proxy)proxy).h; + } + + public static Class getProxyClass(ClassLoader loader, Class[] interfaces) { + Enhancer e = new Enhancer(); + e.setSuperclass(ProxyImpl.class); + e.setInterfaces(interfaces); + e.setCallbackTypes(new Class[]{ + InvocationHandler.class, + NoOp.class, + }); + e.setCallbackFilter(BAD_OBJECT_METHOD_FILTER); + e.setUseFactory(false); + return e.createClass(); + } + + public static boolean isProxyClass(Class cl) { + return cl.getSuperclass().equals(ProxyImpl.class); + } + + public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) { + try { + Class clazz = getProxyClass(loader, interfaces); + return clazz.getConstructor(new Class[]{ InvocationHandler.class }).newInstance(new Object[]{ h }); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/ProxyRefDispatcher.java b/spring-core/src/main/java/org/springframework/cglib/proxy/ProxyRefDispatcher.java new file mode 100644 index 0000000000..84867c79e2 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/ProxyRefDispatcher.java @@ -0,0 +1,31 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.proxy; + +/** + * Dispatching {@link Enhancer} callback. This is the same as the + * {@link Dispatcher} except for the addition of an argument + * which references the proxy object. + */ +public interface ProxyRefDispatcher extends Callback { + /** + * Return the object which the original method invocation should + * be dispatched. This method is called for every method invocation. + * @param proxy a reference to the proxy (generated) object + * @return an object that can invoke the method + */ + Object loadObject(Object proxy) throws Exception; +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java b/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java new file mode 100644 index 0000000000..f566e560ef --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/UndeclaredThrowableException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002,2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ + +package org.springframework.cglib.proxy; + +import org.springframework.cglib.core.CodeGenerationException; + +/** + * Used by {@link Proxy} as a replacement for java.lang.reflect.UndeclaredThrowableException. + * @author Juozas Baliuka + */ +@SuppressWarnings("serial") +public class UndeclaredThrowableException extends CodeGenerationException { + /** + * Creates a new instance of UndeclaredThrowableException without detail message. + */ + public UndeclaredThrowableException(Throwable t) { + super(t); + } + + public Throwable getUndeclaredThrowable() { + return getCause(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java b/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java new file mode 100644 index 0000000000..331b78a093 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/ConstructorDelegate.java @@ -0,0 +1,124 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Type; + +/** + * @author Chris Nokleberg + * @version $Id: ConstructorDelegate.java,v 1.20 2006/03/05 02:43:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class ConstructorDelegate { + private static final ConstructorKey KEY_FACTORY = + (ConstructorKey)KeyFactory.create(ConstructorKey.class, KeyFactory.CLASS_BY_NAME); + + interface ConstructorKey { + public Object newInstance(String declaring, String iface); + } + + protected ConstructorDelegate() { + } + + public static ConstructorDelegate create(Class targetClass, Class iface) { + Generator gen = new Generator(); + gen.setTargetClass(targetClass); + gen.setInterface(iface); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(ConstructorDelegate.class.getName()); + private static final Type CONSTRUCTOR_DELEGATE = + TypeUtils.parseType("org.springframework.cglib.reflect.ConstructorDelegate"); + + private Class iface; + private Class targetClass; + + public Generator() { + super(SOURCE); + } + + public void setInterface(Class iface) { + this.iface = iface; + } + + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + public ConstructorDelegate create() { + setNamePrefix(targetClass.getName()); + Object key = KEY_FACTORY.newInstance(iface.getName(), targetClass.getName()); + return (ConstructorDelegate)super.create(key); + } + + protected ClassLoader getDefaultClassLoader() { + return targetClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(targetClass); + } + + public void generateClass(ClassVisitor v) { + setNamePrefix(targetClass.getName()); + + final Method newInstance = ReflectUtils.findNewInstance(iface); + if (!newInstance.getReturnType().isAssignableFrom(targetClass)) { + throw new IllegalArgumentException("incompatible return type"); + } + final Constructor constructor; + try { + constructor = targetClass.getDeclaredConstructor(newInstance.getParameterTypes()); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("interface does not match any known constructor"); + } + + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_8, + Constants.ACC_PUBLIC, + getClassName(), + CONSTRUCTOR_DELEGATE, + new Type[]{ Type.getType(iface) }, + Constants.SOURCE_FILE); + Type declaring = Type.getType(constructor.getDeclaringClass()); + EmitUtils.null_constructor(ce); + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, + ReflectUtils.getSignature(newInstance), + ReflectUtils.getExceptionTypes(newInstance)); + e.new_instance(declaring); + e.dup(); + e.load_args(); + e.invoke_constructor(declaring, ReflectUtils.getSignature(constructor)); + e.return_value(); + e.end_method(); + ce.end_class(); + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java new file mode 100644 index 0000000000..0bc911b243 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClass.java @@ -0,0 +1,208 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import org.springframework.cglib.core.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class FastClass +{ + private Class type; + + protected FastClass() { + throw new Error("Using the FastClass empty constructor--please report to the cglib-devel mailing list"); + } + + protected FastClass(Class type) { + this.type = type; + } + + public static FastClass create(Class type) { + + return create(type.getClassLoader(),type); + + } + public static FastClass create(ClassLoader loader, Class type) { + Generator gen = new Generator(); + gen.setType(type); + gen.setClassLoader(loader); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator + { + private static final Source SOURCE = new Source(FastClass.class.getName()); + private Class type; + + public Generator() { + super(SOURCE); + } + + public void setType(Class type) { + this.type = type; + } + + public FastClass create() { + setNamePrefix(type.getName()); + return (FastClass)super.create(type.getName()); + } + + protected ClassLoader getDefaultClassLoader() { + return type.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(type); + } + + public void generateClass(ClassVisitor v) throws Exception { + new FastClassEmitter(v, getClassName(), type); + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type, + new Class[]{ Class.class }, + new Object[]{ this.type }); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } + + public Object invoke(String name, Class[] parameterTypes, Object obj, Object[] args) throws InvocationTargetException { + return invoke(getIndex(name, parameterTypes), obj, args); + } + + public Object newInstance() throws InvocationTargetException { + return newInstance(getIndex(Constants.EMPTY_CLASS_ARRAY), null); + } + + public Object newInstance(Class[] parameterTypes, Object[] args) throws InvocationTargetException { + return newInstance(getIndex(parameterTypes), args); + } + + public FastMethod getMethod(Method method) { + return new FastMethod(this, method); + } + + public FastConstructor getConstructor(Constructor constructor) { + return new FastConstructor(this, constructor); + } + + public FastMethod getMethod(String name, Class[] parameterTypes) { + try { + return getMethod(type.getMethod(name, parameterTypes)); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + } + + public FastConstructor getConstructor(Class[] parameterTypes) { + try { + return getConstructor(type.getConstructor(parameterTypes)); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + } + + public String getName() { + return type.getName(); + } + + public Class getJavaClass() { + return type; + } + + public String toString() { + return type.toString(); + } + + public int hashCode() { + return type.hashCode(); + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof FastClass)) { + return false; + } + return type.equals(((FastClass)o).type); + } + + /** + * Return the index of the matching method. The index may be used + * later to invoke the method with less overhead. If more than one + * method matches (i.e. they differ by return type only), one is + * chosen arbitrarily. + * @see #invoke(int, Object, Object[]) + * @param name the method name + * @param parameterTypes the parameter array + * @return the index, or -1 if none is found. + */ + abstract public int getIndex(String name, Class[] parameterTypes); + + /** + * Return the index of the matching constructor. The index may be used + * later to create a new instance with less overhead. + * @see #newInstance(int, Object[]) + * @param parameterTypes the parameter array + * @return the constructor index, or -1 if none is found. + */ + abstract public int getIndex(Class[] parameterTypes); + + /** + * Invoke the method with the specified index. + * @see getIndex(name, Class[]) + * @param index the method index + * @param obj the object the underlying method is invoked from + * @param args the arguments used for the method call + * @throws java.lang.reflect.InvocationTargetException if the underlying method throws an exception + */ + abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException; + + /** + * Create a new instance using the specified constructor index and arguments. + * @see getIndex(Class[]) + * @param index the constructor index + * @param args the arguments passed to the constructor + * @throws java.lang.reflect.InvocationTargetException if the constructor throws an exception + */ + abstract public Object newInstance(int index, Object[] args) throws InvocationTargetException; + + abstract public int getIndex(Signature sig); + + /** + * Returns the maximum method index for this class. + */ + abstract public int getMaxIndex(); + + protected static String getSignatureWithoutReturnType(String name, Class[] parameterTypes) { + StringBuffer sb = new StringBuffer(); + sb.append(name); + sb.append('('); + for (int i = 0; i < parameterTypes.length; i++) { + sb.append(Type.getDescriptor(parameterTypes[i])); + } + sb.append(')'); + return sb.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java new file mode 100644 index 0000000000..11b45ea19d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastClassEmitter.java @@ -0,0 +1,227 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.*; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked", "deprecation"}) +class FastClassEmitter extends ClassEmitter { + private static final Signature CSTRUCT_CLASS = + TypeUtils.parseConstructor("Class"); + private static final Signature METHOD_GET_INDEX = + TypeUtils.parseSignature("int getIndex(String, Class[])"); + private static final Signature SIGNATURE_GET_INDEX = + new Signature("getIndex", Type.INT_TYPE, new Type[]{ Constants.TYPE_SIGNATURE }); + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + private static final Signature CONSTRUCTOR_GET_INDEX = + TypeUtils.parseSignature("int getIndex(Class[])"); + private static final Signature INVOKE = + TypeUtils.parseSignature("Object invoke(int, Object, Object[])"); + private static final Signature NEW_INSTANCE = + TypeUtils.parseSignature("Object newInstance(int, Object[])"); + private static final Signature GET_MAX_INDEX = + TypeUtils.parseSignature("int getMaxIndex()"); + private static final Signature GET_SIGNATURE_WITHOUT_RETURN_TYPE = + TypeUtils.parseSignature("String getSignatureWithoutReturnType(String, Class[])"); + private static final Type FAST_CLASS = + TypeUtils.parseType("org.springframework.cglib.reflect.FastClass"); + private static final Type ILLEGAL_ARGUMENT_EXCEPTION = + TypeUtils.parseType("IllegalArgumentException"); + private static final Type INVOCATION_TARGET_EXCEPTION = + TypeUtils.parseType("java.lang.reflect.InvocationTargetException"); + private static final Type[] INVOCATION_TARGET_EXCEPTION_ARRAY = { INVOCATION_TARGET_EXCEPTION }; + + public FastClassEmitter(ClassVisitor v, String className, Class type) { + super(v); + + Type base = Type.getType(type); + begin_class(Constants.V1_8, Constants.ACC_PUBLIC, className, FAST_CLASS, null, Constants.SOURCE_FILE); + + // constructor + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_CLASS, null); + e.load_this(); + e.load_args(); + e.super_invoke_constructor(CSTRUCT_CLASS); + e.return_value(); + e.end_method(); + + VisibilityPredicate vp = new VisibilityPredicate(type, false); + List methods = ReflectUtils.addAllMethods(type, new ArrayList()); + CollectionUtils.filter(methods, vp); + CollectionUtils.filter(methods, new DuplicatesPredicate()); + List constructors = new ArrayList(Arrays.asList(type.getDeclaredConstructors())); + CollectionUtils.filter(constructors, vp); + + // getIndex(String) + emitIndexBySignature(methods); + + // getIndex(String, Class[]) + emitIndexByClassArray(methods); + + // getIndex(Class[]) + e = begin_method(Constants.ACC_PUBLIC, CONSTRUCTOR_GET_INDEX, null); + e.load_args(); + List info = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance()); + EmitUtils.constructor_switch(e, info, new GetIndexCallback(e, info)); + e.end_method(); + + // invoke(int, Object, Object[]) + e = begin_method(Constants.ACC_PUBLIC, INVOKE, INVOCATION_TARGET_EXCEPTION_ARRAY); + e.load_arg(1); + e.checkcast(base); + e.load_arg(0); + invokeSwitchHelper(e, methods, 2, base); + e.end_method(); + + // newInstance(int, Object[]) + e = begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, INVOCATION_TARGET_EXCEPTION_ARRAY); + e.new_instance(base); + e.dup(); + e.load_arg(0); + invokeSwitchHelper(e, constructors, 1, base); + e.end_method(); + + // getMaxIndex() + e = begin_method(Constants.ACC_PUBLIC, GET_MAX_INDEX, null); + e.push(methods.size() - 1); + e.return_value(); + e.end_method(); + + end_class(); + } + + // TODO: support constructor indices ("") + private void emitIndexBySignature(List methods) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SIGNATURE_GET_INDEX, null); + List signatures = CollectionUtils.transform(methods, new Transformer() { + public Object transform(Object obj) { + return ReflectUtils.getSignature((Method)obj).toString(); + } + }); + e.load_arg(0); + e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING); + signatureSwitchHelper(e, signatures); + e.end_method(); + } + + private static final int TOO_MANY_METHODS = 100; // TODO + private void emitIndexByClassArray(List methods) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, METHOD_GET_INDEX, null); + if (methods.size() > TOO_MANY_METHODS) { + // hack for big classes + List signatures = CollectionUtils.transform(methods, new Transformer() { + public Object transform(Object obj) { + String s = ReflectUtils.getSignature((Method)obj).toString(); + return s.substring(0, s.lastIndexOf(')') + 1); + } + }); + e.load_args(); + e.invoke_static(FAST_CLASS, GET_SIGNATURE_WITHOUT_RETURN_TYPE); + signatureSwitchHelper(e, signatures); + } else { + e.load_args(); + List info = CollectionUtils.transform(methods, MethodInfoTransformer.getInstance()); + EmitUtils.method_switch(e, info, new GetIndexCallback(e, info)); + } + e.end_method(); + } + + private void signatureSwitchHelper(final CodeEmitter e, final List signatures) { + ObjectSwitchCallback callback = new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + // TODO: remove linear indexOf + e.push(signatures.indexOf(key)); + e.return_value(); + } + public void processDefault() { + e.push(-1); + e.return_value(); + } + }; + EmitUtils.string_switch(e, + (String[])signatures.toArray(new String[signatures.size()]), + Constants.SWITCH_STYLE_HASH, + callback); + } + + private static void invokeSwitchHelper(final CodeEmitter e, List members, final int arg, final Type base) { + final List info = CollectionUtils.transform(members, MethodInfoTransformer.getInstance()); + final Label illegalArg = e.make_label(); + Block block = e.begin_block(); + e.process_switch(getIntRange(info.size()), new ProcessSwitchCallback() { + public void processCase(int key, Label end) { + MethodInfo method = (MethodInfo)info.get(key); + Type[] types = method.getSignature().getArgumentTypes(); + for (int i = 0; i < types.length; i++) { + e.load_arg(arg); + e.aaload(i); + e.unbox(types[i]); + } + // TODO: change method lookup process so MethodInfo will already reference base + // instead of superclass when superclass method is inaccessible + e.invoke(method, base); + if (!TypeUtils.isConstructor(method)) { + e.box(method.getSignature().getReturnType()); + } + e.return_value(); + } + public void processDefault() { + e.goTo(illegalArg); + } + }); + block.end(); + EmitUtils.wrap_throwable(block, INVOCATION_TARGET_EXCEPTION); + e.mark(illegalArg); + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Cannot find matching method/constructor"); + } + + private static class GetIndexCallback implements ObjectSwitchCallback { + private CodeEmitter e; + private Map indexes = new HashMap(); + + public GetIndexCallback(CodeEmitter e, List methods) { + this.e = e; + int index = 0; + for (Iterator it = methods.iterator(); it.hasNext();) { + indexes.put(it.next(), index++); + } + } + + public void processCase(Object key, Label end) { + e.push(((Integer)indexes.get(key))); + e.return_value(); + } + + public void processDefault() { + e.push(-1); + e.return_value(); + } + } + + private static int[] getIntRange(int length) { + int[] range = new int[length]; + for (int i = 0; i < length; i++) { + range[i] = i; + } + return range; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java new file mode 100644 index 0000000000..b3735dd141 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastConstructor.java @@ -0,0 +1,47 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class FastConstructor extends FastMember +{ + FastConstructor(FastClass fc, Constructor constructor) { + super(fc, constructor, fc.getIndex(constructor.getParameterTypes())); + } + + public Class[] getParameterTypes() { + return ((Constructor)member).getParameterTypes(); + } + + public Class[] getExceptionTypes() { + return ((Constructor)member).getExceptionTypes(); + } + + public Object newInstance() throws InvocationTargetException { + return fc.newInstance(index, null); + } + + public Object newInstance(Object[] args) throws InvocationTargetException { + return fc.newInstance(index, args); + } + + public Constructor getJavaConstructor() { + return (Constructor)member; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java new file mode 100644 index 0000000000..f6c3ca7e5b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMember.java @@ -0,0 +1,66 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.Member; + +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class FastMember +{ + protected FastClass fc; + protected Member member; + protected int index; + + protected FastMember(FastClass fc, Member member, int index) { + this.fc = fc; + this.member = member; + this.index = index; + } + + abstract public Class[] getParameterTypes(); + abstract public Class[] getExceptionTypes(); + + public int getIndex() { + return index; + } + + public String getName() { + return member.getName(); + } + + public Class getDeclaringClass() { + return fc.getJavaClass(); + } + + public int getModifiers() { + return member.getModifiers(); + } + + public String toString() { + return member.toString(); + } + + public int hashCode() { + return member.hashCode(); + } + + public boolean equals(Object o) { + if (o == null || !(o instanceof FastMember)) { + return false; + } + return member.equals(((FastMember)o).member); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java new file mode 100644 index 0000000000..323919298a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/FastMethod.java @@ -0,0 +1,64 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springframework.cglib.core.Signature; + +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class FastMethod extends FastMember +{ + FastMethod(FastClass fc, Method method) { + super(fc, method, helper(fc, method)); + } + + private static int helper(FastClass fc, Method method) { + int index = fc.getIndex(new Signature(method.getName(), Type.getMethodDescriptor(method))); + if (index < 0) { + Class[] types = method.getParameterTypes(); + System.err.println("hash=" + method.getName().hashCode() + " size=" + types.length); + for (int i = 0; i < types.length; i++) { + System.err.println(" types[" + i + "]=" + types[i].getName()); + } + throw new IllegalArgumentException("Cannot find method " + method); + } + return index; + } + + public Class getReturnType() { + return ((Method)member).getReturnType(); + } + + public Class[] getParameterTypes() { + return ((Method)member).getParameterTypes(); + } + + public Class[] getExceptionTypes() { + return ((Method)member).getExceptionTypes(); + } + + public Object invoke(Object obj, Object[] args) throws InvocationTargetException { + return fc.invoke(index, obj, args); + } + + public Method getJavaMethod() { + return (Method)member; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/MethodDelegate.java b/spring-core/src/main/java/org/springframework/cglib/reflect/MethodDelegate.java new file mode 100644 index 0000000000..bfe8bcd592 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/MethodDelegate.java @@ -0,0 +1,269 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import org.springframework.cglib.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Type; + +// TODO: don't require exact match for return type + +/** + * DOCUMENTATION FROM APACHE AVALON DELEGATE CLASS + * + *

+ * Delegates are a typesafe pointer to another method. Since Java does not + * have language support for such a construct, this utility will construct + * a proxy that forwards method calls to any method with the same signature. + * This utility is inspired in part by the C# delegate mechanism. We + * implemented it in a Java-centric manner. + *

+ * + *

Delegate

+ *

+ * Any interface with one method can become the interface for a delegate. + * Consider the example below: + *

+ * + *
+ *   public interface MainDelegate {
+ *       int main(String[] args);
+ *   }
+ * 
+ * + *

+ * The interface above is an example of an interface that can become a + * delegate. It has only one method, and the interface is public. In + * order to create a delegate for that method, all we have to do is + * call MethodDelegate.create(this, "alternateMain", MainDelegate.class). + * The following program will show how to use it: + *

+ * + *
+ *   public class Main {
+ *       public static int main( String[] args ) {
+ *           Main newMain = new Main();
+ *           MainDelegate start = (MainDelegate)
+ *               MethodDelegate.create(newMain, "alternateMain", MainDelegate.class);
+ *           return start.main( args );
+ *       }
+ *
+ *       public int alternateMain( String[] args ) {
+ *           for (int i = 0; i < args.length; i++) {
+ *               System.out.println( args[i] );
+ *           }
+ *           return args.length;
+ *       }
+ *   }
+ * 
+ * + *

+ * By themselves, delegates don't do much. Their true power lies in the fact that + * they can be treated like objects, and passed to other methods. In fact that is + * one of the key building blocks of building Intelligent Agents which in tern are + * the foundation of artificial intelligence. In the above program, we could have + * easily created the delegate to match the static main method by + * substituting the delegate creation call with this: + * MethodDelegate.createStatic(getClass(), "main", MainDelegate.class). + *

+ *

+ * Another key use for Delegates is to register event listeners. It is much easier + * to have all the code for your events separated out into methods instead of individual + * classes. One of the ways Java gets around that is to create anonymous classes. + * They are particularly troublesome because many Debuggers do not know what to do + * with them. Anonymous classes tend to duplicate alot of code as well. We can + * use any interface with one declared method to forward events to any method that + * matches the signature (although the method name can be different). + *

+ * + *

Equality

+ * The criteria that we use to test if two delegates are equal are: + *
    + *
  • + * They both refer to the same instance. That is, the instance + * parameter passed to the newDelegate method was the same for both. The + * instances are compared with the identity equality operator, ==. + *
  • + *
  • They refer to the same method as resolved by Method.equals.
  • + *
+ * + * @version $Id: MethodDelegate.java,v 1.25 2006/03/05 02:43:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class MethodDelegate { + private static final MethodDelegateKey KEY_FACTORY = + (MethodDelegateKey)KeyFactory.create(MethodDelegateKey.class, KeyFactory.CLASS_BY_NAME); + + protected Object target; + protected String eqMethod; + + interface MethodDelegateKey { + Object newInstance(Class delegateClass, String methodName, Class iface); + } + + public static MethodDelegate createStatic(Class targetClass, String methodName, Class iface) { + Generator gen = new Generator(); + gen.setTargetClass(targetClass); + gen.setMethodName(methodName); + gen.setInterface(iface); + return gen.create(); + } + + public static MethodDelegate create(Object target, String methodName, Class iface) { + Generator gen = new Generator(); + gen.setTarget(target); + gen.setMethodName(methodName); + gen.setInterface(iface); + return gen.create(); + } + + public boolean equals(Object obj) { + MethodDelegate other = (MethodDelegate)obj; + return (other != null && target == other.target) && eqMethod.equals(other.eqMethod); + } + + public int hashCode() { + return target.hashCode() ^ eqMethod.hashCode(); + } + + public Object getTarget() { + return target; + } + + abstract public MethodDelegate newInstance(Object target); + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(MethodDelegate.class.getName()); + private static final Type METHOD_DELEGATE = + TypeUtils.parseType("org.springframework.cglib.reflect.MethodDelegate"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", METHOD_DELEGATE, new Type[]{ Constants.TYPE_OBJECT }); + + private Object target; + private Class targetClass; + private String methodName; + private Class iface; + + public Generator() { + super(SOURCE); + } + + public void setTarget(Object target) { + this.target = target; + this.targetClass = target.getClass(); + } + + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public void setInterface(Class iface) { + this.iface = iface; + } + + protected ClassLoader getDefaultClassLoader() { + return targetClass.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(targetClass); + } + + public MethodDelegate create() { + setNamePrefix(targetClass.getName()); + Object key = KEY_FACTORY.newInstance(targetClass, methodName, iface); + return (MethodDelegate)super.create(key); + } + + protected Object firstInstance(Class type) { + return ((MethodDelegate)ReflectUtils.newInstance(type)).newInstance(target); + } + + protected Object nextInstance(Object instance) { + return ((MethodDelegate)instance).newInstance(target); + } + + public void generateClass(ClassVisitor v) throws NoSuchMethodException { + Method proxy = ReflectUtils.findInterfaceMethod(iface); + final Method method = targetClass.getMethod(methodName, proxy.getParameterTypes()); + if (!proxy.getReturnType().isAssignableFrom(method.getReturnType())) { + throw new IllegalArgumentException("incompatible return types"); + } + + MethodInfo methodInfo = ReflectUtils.getMethodInfo(method); + + boolean isStatic = TypeUtils.isStatic(methodInfo.getModifiers()); + if ((target == null) ^ isStatic) { + throw new IllegalArgumentException("Static method " + (isStatic ? "not " : "") + "expected"); + } + + ClassEmitter ce = new ClassEmitter(v); + CodeEmitter e; + ce.begin_class(Constants.V1_8, + Constants.ACC_PUBLIC, + getClassName(), + METHOD_DELEGATE, + new Type[]{ Type.getType(iface) }, + Constants.SOURCE_FILE); + ce.declare_field(Constants.PRIVATE_FINAL_STATIC, "eqMethod", Constants.TYPE_STRING, null); + EmitUtils.null_constructor(ce); + + // generate proxied method + MethodInfo proxied = ReflectUtils.getMethodInfo(iface.getDeclaredMethods()[0]); + int modifiers = Constants.ACC_PUBLIC; + if ((proxied.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + modifiers |= Constants.ACC_VARARGS; + } + e = EmitUtils.begin_method(ce, proxied, modifiers); + e.load_this(); + e.super_getfield("target", Constants.TYPE_OBJECT); + e.checkcast(methodInfo.getClassInfo().getType()); + e.load_args(); + e.invoke(methodInfo); + e.return_value(); + e.end_method(); + + // newInstance + e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null); + e.new_instance_this(); + e.dup(); + e.dup2(); + e.invoke_constructor_this(); + e.getfield("eqMethod"); + e.super_putfield("eqMethod", Constants.TYPE_STRING); + e.load_arg(0); + e.super_putfield("target", Constants.TYPE_OBJECT); + e.return_value(); + e.end_method(); + + // static initializer + e = ce.begin_static(); + e.push(methodInfo.getSignature().toString()); + e.putfield("eqMethod"); + e.return_value(); + e.end_method(); + + ce.end_class(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java b/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java new file mode 100644 index 0000000000..1842d4f051 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/reflect/MulticastDelegate.java @@ -0,0 +1,180 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.reflect; + +import java.lang.reflect.*; +import java.security.ProtectionDomain; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class MulticastDelegate implements Cloneable { + protected Object[] targets = {}; + + protected MulticastDelegate() { + } + + public List getTargets() { + return new ArrayList(Arrays.asList(targets)); + } + + abstract public MulticastDelegate add(Object target); + + protected MulticastDelegate addHelper(Object target) { + MulticastDelegate copy = newInstance(); + copy.targets = new Object[targets.length + 1]; + System.arraycopy(targets, 0, copy.targets, 0, targets.length); + copy.targets[targets.length] = target; + return copy; + } + + public MulticastDelegate remove(Object target) { + for (int i = targets.length - 1; i >= 0; i--) { + if (targets[i].equals(target)) { + MulticastDelegate copy = newInstance(); + copy.targets = new Object[targets.length - 1]; + System.arraycopy(targets, 0, copy.targets, 0, i); + System.arraycopy(targets, i + 1, copy.targets, i, targets.length - i - 1); + return copy; + } + } + return this; + } + + abstract public MulticastDelegate newInstance(); + + public static MulticastDelegate create(Class iface) { + Generator gen = new Generator(); + gen.setInterface(iface); + return gen.create(); + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(MulticastDelegate.class.getName()); + private static final Type MULTICAST_DELEGATE = + TypeUtils.parseType("org.springframework.cglib.reflect.MulticastDelegate"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", MULTICAST_DELEGATE, new Type[0]); + private static final Signature ADD_DELEGATE = + new Signature("add", MULTICAST_DELEGATE, new Type[]{ Constants.TYPE_OBJECT }); + private static final Signature ADD_HELPER = + new Signature("addHelper", MULTICAST_DELEGATE, new Type[]{ Constants.TYPE_OBJECT }); + + private Class iface; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return iface.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(iface); + } + + public void setInterface(Class iface) { + this.iface = iface; + } + + public MulticastDelegate create() { + setNamePrefix(MulticastDelegate.class.getName()); + return (MulticastDelegate)super.create(iface.getName()); + } + + public void generateClass(ClassVisitor cv) { + final MethodInfo method = ReflectUtils.getMethodInfo(ReflectUtils.findInterfaceMethod(iface)); + + ClassEmitter ce = new ClassEmitter(cv); + ce.begin_class(Constants.V1_8, + Constants.ACC_PUBLIC, + getClassName(), + MULTICAST_DELEGATE, + new Type[]{ Type.getType(iface) }, + Constants.SOURCE_FILE); + EmitUtils.null_constructor(ce); + + // generate proxied method + emitProxy(ce, method); + + // newInstance + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null); + e.new_instance_this(); + e.dup(); + e.invoke_constructor_this(); + e.return_value(); + e.end_method(); + + // add + e = ce.begin_method(Constants.ACC_PUBLIC, ADD_DELEGATE, null); + e.load_this(); + e.load_arg(0); + e.checkcast(Type.getType(iface)); + e.invoke_virtual_this(ADD_HELPER); + e.return_value(); + e.end_method(); + + ce.end_class(); + } + + private void emitProxy(ClassEmitter ce, final MethodInfo method) { + int modifiers = Constants.ACC_PUBLIC; + if ((method.getModifiers() & Constants.ACC_VARARGS) == Constants.ACC_VARARGS) { + modifiers |= Constants.ACC_VARARGS; + } + final CodeEmitter e = EmitUtils.begin_method(ce, method, modifiers); + Type returnType = method.getSignature().getReturnType(); + final boolean returns = returnType != Type.VOID_TYPE; + Local result = null; + if (returns) { + result = e.make_local(returnType); + e.zero_or_null(returnType); + e.store_local(result); + } + e.load_this(); + e.super_getfield("targets", Constants.TYPE_OBJECT_ARRAY); + final Local result2 = result; + EmitUtils.process_array(e, Constants.TYPE_OBJECT_ARRAY, new ProcessArrayCallback() { + public void processElement(Type type) { + e.checkcast(Type.getType(iface)); + e.load_args(); + e.invoke(method); + if (returns) { + e.store_local(result2); + } + } + }); + if (returns) { + e.load_local(result); + } + e.return_value(); + e.end_method(); + } + + protected Object firstInstance(Class type) { + // make a new instance in case first object is used with a long list of targets + return ((MulticastDelegate)ReflectUtils.newInstance(type)).newInstance(); + } + + protected Object nextInstance(Object instance) { + return ((MulticastDelegate)instance).newInstance(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java new file mode 100644 index 0000000000..e82cc31a0d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassFilterTransformer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.asm.*; + +abstract public class AbstractClassFilterTransformer extends AbstractClassTransformer { + private ClassTransformer pass; + private ClassVisitor target; + + public void setTarget(ClassVisitor target) { + super.setTarget(target); + pass.setTarget(target); + } + + protected AbstractClassFilterTransformer(ClassTransformer pass) { + this.pass = pass; + } + + abstract protected boolean accept(int version, int access, String name, String signature, String superName, String[] interfaces); + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + target = accept(version, access, name, signature, superName, interfaces) ? pass : cv; + target.visit(version, access, name, signature, superName, interfaces); + } + + public void visitSource(String source, String debug) { + target.visitSource(source, debug); + } + + public void visitOuterClass(String owner, String name, String desc) { + target.visitOuterClass(owner, name, desc); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return target.visitAnnotation(desc, visible); + } + + public void visitAttribute(Attribute attr) { + target.visitAttribute(attr); + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + target.visitInnerClass(name, outerName, innerName, access); + } + + public FieldVisitor visitField(int access, + String name, + String desc, + String signature, + Object value) { + return target.visitField(access, name, desc, signature, value); + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return target.visitMethod(access, name, desc, signature, exceptions); + } + + public void visitEnd() { + target.visitEnd(); + target = null; // just to be safe + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java new file mode 100644 index 0000000000..293037bb9a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassLoader.java @@ -0,0 +1,112 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.CodeGenerationException; +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.cglib.core.DebuggingClassWriter; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassWriter; +import org.springframework.asm.Attribute; + +import java.io.IOException; + +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class AbstractClassLoader extends ClassLoader { + private ClassFilter filter; + private ClassLoader classPath; + private static java.security.ProtectionDomain DOMAIN ; + + static{ + DOMAIN = AbstractClassLoader.class.getProtectionDomain(); + } + + protected AbstractClassLoader(ClassLoader parent, ClassLoader classPath, ClassFilter filter) { + super(parent); + this.filter = filter; + this.classPath = classPath; + } + + public Class loadClass(String name) throws ClassNotFoundException { + + Class loaded = findLoadedClass(name); + + if( loaded != null ){ + if( loaded.getClassLoader() == this ){ + return loaded; + }//else reload with this class loader + } + + if (!filter.accept(name)) { + return super.loadClass(name); + } + ClassReader r; + try { + + java.io.InputStream is = classPath.getResourceAsStream( + name.replace('.','/') + ".class" + ); + + if (is == null) { + + throw new ClassNotFoundException(name); + + } + try { + + r = new ClassReader(is); + + } finally { + + is.close(); + + } + } catch (IOException e) { + throw new ClassNotFoundException(name + ":" + e.getMessage()); + } + + try { + DebuggingClassWriter w = + new DebuggingClassWriter(ClassWriter.COMPUTE_FRAMES); + getGenerator(r).generateClass(w); + byte[] b = w.toByteArray(); + Class c = super.defineClass(name, b, 0, b.length, DOMAIN); + postProcess(c); + return c; + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + + protected ClassGenerator getGenerator(ClassReader r) { + return new ClassReaderGenerator(r, attributes(), getFlags()); + } + + protected int getFlags() { + return 0; + } + + protected Attribute[] attributes() { + return null; + } + + protected void postProcess(Class c) { + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java new file mode 100644 index 0000000000..7236f1d50e --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AbstractClassTransformer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.ClassVisitor; + +abstract public class AbstractClassTransformer extends ClassTransformer { + protected AbstractClassTransformer() { + super(Constants.ASM_API); + } + + public void setTarget(ClassVisitor target) { + cv = target; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java new file mode 100644 index 0000000000..f194d9a037 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/AnnotationVisitorTee.java @@ -0,0 +1,61 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.AnnotationVisitor; + +public class AnnotationVisitorTee extends AnnotationVisitor { + private AnnotationVisitor av1, av2; + + public static AnnotationVisitor getInstance(AnnotationVisitor av1, AnnotationVisitor av2) { + if (av1 == null) + return av2; + if (av2 == null) + return av1; + return new AnnotationVisitorTee(av1, av2); + } + + public AnnotationVisitorTee(AnnotationVisitor av1, AnnotationVisitor av2) { + super(Constants.ASM_API); + this.av1 = av1; + this.av2 = av2; + } + + public void visit(String name, Object value) { + av2.visit(name, value); + av2.visit(name, value); + } + + public void visitEnum(String name, String desc, String value) { + av1.visitEnum(name, desc, value); + av2.visitEnum(name, desc, value); + } + + public AnnotationVisitor visitAnnotation(String name, String desc) { + return getInstance(av1.visitAnnotation(name, desc), + av2.visitAnnotation(name, desc)); + } + + public AnnotationVisitor visitArray(String name) { + return getInstance(av1.visitArray(name), av2.visitArray(name)); + } + + public void visitEnd() { + av1.visitEnd(); + av2.visitEnd(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassEmitterTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassEmitterTransformer.java new file mode 100644 index 0000000000..60236b0a99 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassEmitterTransformer.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.ClassEmitter; + +abstract public class ClassEmitterTransformer extends ClassEmitter { +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java new file mode 100644 index 0000000000..adb63550fd --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ + +package org.springframework.cglib.transform; + +/** + * + * @author baliuka + */ +public interface ClassFilter { + + boolean accept(String className); + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilterTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilterTransformer.java new file mode 100644 index 0000000000..d4690db6c8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassFilterTransformer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.asm.*; + +public class ClassFilterTransformer extends AbstractClassFilterTransformer { + private ClassFilter filter; + + public ClassFilterTransformer(ClassFilter filter, ClassTransformer pass) { + super(pass); + this.filter = filter; + } + + protected boolean accept(int version, int access, String name, String signature, String superName, String[] interfaces) { + return filter.accept(name.replace('/', '.')); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java new file mode 100644 index 0000000000..152d4089de --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassReaderGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.asm.Attribute; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; + +public class ClassReaderGenerator implements ClassGenerator { + private final ClassReader r; + private final Attribute[] attrs; + private final int flags; + + public ClassReaderGenerator(ClassReader r, int flags) { + this(r, null, flags); + } + + public ClassReaderGenerator(ClassReader r, Attribute[] attrs, int flags) { + this.r = r; + this.attrs = (attrs != null) ? attrs : new Attribute[0]; + this.flags = flags; + } + + public void generateClass(ClassVisitor v) { + r.accept(v, attrs, flags); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformer.java new file mode 100644 index 0000000000..0c678d9b50 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.ClassVisitor; + +public abstract class ClassTransformer extends ClassVisitor { + public ClassTransformer() { + super(Constants.ASM_API); + } + public ClassTransformer(int opcode) { + super(opcode); + } + public abstract void setTarget(ClassVisitor target); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java new file mode 100644 index 0000000000..482464ebee --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerChain.java @@ -0,0 +1,56 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.asm.*; + +public class ClassTransformerChain extends AbstractClassTransformer { + private ClassTransformer[] chain; + + public ClassTransformerChain(ClassTransformer[] chain) { + this.chain = chain.clone(); + } + + public void setTarget(ClassVisitor v) { + super.setTarget(chain[0]); + ClassVisitor next = v; + for (int i = chain.length - 1; i >= 0; i--) { + chain[i].setTarget(next); + next = chain[i]; + } + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return cv.visitMethod(access, name, desc, signature, exceptions); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("ClassTransformerChain{"); + for (int i = 0; i < chain.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(chain[i].toString()); + } + sb.append("}"); + return sb.toString(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerFactory.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerFactory.java new file mode 100644 index 0000000000..8f9305a627 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +public interface ClassTransformerFactory { + ClassTransformer newInstance(); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java new file mode 100644 index 0000000000..1588a0ac66 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassTransformerTee.java @@ -0,0 +1,32 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.ClassVisitor; + +public class ClassTransformerTee extends ClassTransformer { + private ClassVisitor branch; + + public ClassTransformerTee(ClassVisitor branch) { + super(Constants.ASM_API); + this.branch = branch; + } + + public void setTarget(ClassVisitor target) { + cv = new ClassVisitorTee(branch, target); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java new file mode 100644 index 0000000000..ba94335feb --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/ClassVisitorTee.java @@ -0,0 +1,104 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.*; + +public class ClassVisitorTee extends ClassVisitor { + private ClassVisitor cv1, cv2; + + public ClassVisitorTee(ClassVisitor cv1, ClassVisitor cv2) { + super(Constants.ASM_API); + this.cv1 = cv1; + this.cv2 = cv2; + } + + public void visit(int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + cv1.visit(version, access, name, signature, superName, interfaces); + cv2.visit(version, access, name, signature, superName, interfaces); + } + + public void visitEnd() { + cv1.visitEnd(); + cv2.visitEnd(); + cv1 = cv2 = null; + } + + public void visitInnerClass(String name, String outerName, String innerName, int access) { + cv1.visitInnerClass(name, outerName, innerName, access); + cv2.visitInnerClass(name, outerName, innerName, access); + } + + public FieldVisitor visitField(int access, + String name, + String desc, + String signature, + Object value) { + FieldVisitor fv1 = cv1.visitField(access, name, desc, signature, value); + FieldVisitor fv2 = cv2.visitField(access, name, desc, signature, value); + if (fv1 == null) + return fv2; + if (fv2 == null) + return fv1; + return new FieldVisitorTee(fv1, fv2); + } + + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + MethodVisitor mv1 = cv1.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor mv2 = cv2.visitMethod(access, name, desc, signature, exceptions); + if (mv1 == null) + return mv2; + if (mv2 == null) + return mv1; + return new MethodVisitorTee(mv1, mv2); + } + + public void visitSource(String source, String debug) { + cv1.visitSource(source, debug); + cv2.visitSource(source, debug); + } + + public void visitOuterClass(String owner, String name, String desc) { + cv1.visitOuterClass(owner, name, desc); + cv2.visitOuterClass(owner, name, desc); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(cv1.visitAnnotation(desc, visible), + cv2.visitAnnotation(desc, visible)); + } + + public void visitAttribute(Attribute attrs) { + cv1.visitAttribute(attrs); + cv2.visitAttribute(attrs); + } + + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(cv1.visitTypeAnnotation(typeRef, typePath, desc, visible), + cv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java new file mode 100644 index 0000000000..a6c538099d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/FieldVisitorTee.java @@ -0,0 +1,53 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.AnnotationVisitor; +import org.springframework.asm.Attribute; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.TypePath; + +public class FieldVisitorTee extends FieldVisitor { + private FieldVisitor fv1, fv2; + + public FieldVisitorTee(FieldVisitor fv1, FieldVisitor fv2) { + super(Constants.ASM_API); + this.fv1 = fv1; + this.fv2 = fv2; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(fv1.visitAnnotation(desc, visible), + fv2.visitAnnotation(desc, visible)); + } + + public void visitAttribute(Attribute attr) { + fv1.visitAttribute(attr); + fv2.visitAttribute(attr); + } + + public void visitEnd() { + fv1.visitEnd(); + fv2.visitEnd(); + } + + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(fv1.visitTypeAnnotation(typeRef, typePath, desc, visible), + fv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); + } +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilter.java b/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilter.java new file mode 100644 index 0000000000..401c880dea --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.asm.Attribute; + +public interface MethodFilter { + // TODO: pass class name too? + boolean accept(int access, String name, String desc, String signature, String[] exceptions); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java new file mode 100644 index 0000000000..aa7591dccd --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/MethodFilterTransformer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.asm.*; + +public class MethodFilterTransformer extends AbstractClassTransformer { + private MethodFilter filter; + private ClassTransformer pass; + private ClassVisitor direct; + + public MethodFilterTransformer(MethodFilter filter, ClassTransformer pass) { + this.filter = filter; + this.pass = pass; + super.setTarget(pass); + } + + public MethodVisitor visitMethod(int access, + String name, + String desc, + String signature, + String[] exceptions) { + return (filter.accept(access, name, desc, signature, exceptions) ? pass : direct).visitMethod(access, name, desc, signature, exceptions); + } + + public void setTarget(ClassVisitor target) { + pass.setTarget(target); + direct = target; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java b/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java new file mode 100644 index 0000000000..20470bda9d --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/MethodVisitorTee.java @@ -0,0 +1,189 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.Constants; +import org.springframework.asm.*; + +@SuppressWarnings("deprecation") +public class MethodVisitorTee extends MethodVisitor { + private final MethodVisitor mv1; + private final MethodVisitor mv2; + + public MethodVisitorTee(MethodVisitor mv1, MethodVisitor mv2) { + super(Constants.ASM_API); + this.mv1 = mv1; + this.mv2 = mv2; + } + + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + mv1.visitFrame(type, nLocal, local, nStack, stack); + mv2.visitFrame(type, nLocal, local, nStack, stack); + } + + public AnnotationVisitor visitAnnotationDefault() { + return AnnotationVisitorTee.getInstance(mv1.visitAnnotationDefault(), + mv2.visitAnnotationDefault()); + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitAnnotation(desc, visible), + mv2.visitAnnotation(desc, visible)); + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, + String desc, + boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitParameterAnnotation(parameter, desc, visible), + mv2.visitParameterAnnotation(parameter, desc, visible)); + } + + public void visitAttribute(Attribute attr) { + mv1.visitAttribute(attr); + mv2.visitAttribute(attr); + } + + public void visitCode() { + mv1.visitCode(); + mv2.visitCode(); + } + + public void visitInsn(int opcode) { + mv1.visitInsn(opcode); + mv2.visitInsn(opcode); + } + + public void visitIntInsn(int opcode, int operand) { + mv1.visitIntInsn(opcode, operand); + mv2.visitIntInsn(opcode, operand); + } + + public void visitVarInsn(int opcode, int var) { + mv1.visitVarInsn(opcode, var); + mv2.visitVarInsn(opcode, var); + } + + public void visitTypeInsn(int opcode, String desc) { + mv1.visitTypeInsn(opcode, desc); + mv2.visitTypeInsn(opcode, desc); + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + mv1.visitFieldInsn(opcode, owner, name, desc); + mv2.visitFieldInsn(opcode, owner, name, desc); + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + mv1.visitMethodInsn(opcode, owner, name, desc); + mv2.visitMethodInsn(opcode, owner, name, desc); + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + mv1.visitMethodInsn(opcode, owner, name, desc, itf); + mv2.visitMethodInsn(opcode, owner, name, desc, itf); + } + + public void visitJumpInsn(int opcode, Label label) { + mv1.visitJumpInsn(opcode, label); + mv2.visitJumpInsn(opcode, label); + } + + public void visitLabel(Label label) { + mv1.visitLabel(label); + mv2.visitLabel(label); + } + + public void visitLdcInsn(Object cst) { + mv1.visitLdcInsn(cst); + mv2.visitLdcInsn(cst); + } + + public void visitIincInsn(int var, int increment) { + mv1.visitIincInsn(var, increment); + mv2.visitIincInsn(var, increment); + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + mv1.visitTableSwitchInsn(min, max, dflt, labels); + mv2.visitTableSwitchInsn(min, max, dflt, labels); + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + mv1.visitLookupSwitchInsn(dflt, keys, labels); + mv2.visitLookupSwitchInsn(dflt, keys, labels); + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + mv1.visitMultiANewArrayInsn(desc, dims); + mv2.visitMultiANewArrayInsn(desc, dims); + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + mv1.visitTryCatchBlock(start, end, handler, type); + mv2.visitTryCatchBlock(start, end, handler, type); + } + + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + mv1.visitLocalVariable(name, desc, signature, start, end, index); + mv2.visitLocalVariable(name, desc, signature, start, end, index); + } + + public void visitLineNumber(int line, Label start) { + mv1.visitLineNumber(line, start); + mv2.visitLineNumber(line, start); + } + + public void visitMaxs(int maxStack, int maxLocals) { + mv1.visitMaxs(maxStack, maxLocals); + mv2.visitMaxs(maxStack, maxLocals); + } + + public void visitEnd() { + mv1.visitEnd(); + mv2.visitEnd(); + } + + public void visitParameter(String name, int access) { + mv1.visitParameter(name, access); + mv2.visitParameter(name, access); + } + + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitTypeAnnotation(typeRef, typePath, desc, visible), + mv2.visitTypeAnnotation(typeRef, typePath, desc, visible)); + } + + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + mv1.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + mv2.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitInsnAnnotation(typeRef, typePath, desc, visible), + mv2.visitInsnAnnotation(typeRef, typePath, desc, visible)); + } + + public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitTryCatchAnnotation(typeRef, typePath, desc, visible), + mv2.visitTryCatchAnnotation(typeRef, typePath, desc, visible)); + } + + public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { + return AnnotationVisitorTee.getInstance(mv1.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible), + mv2.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible)); + } +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassGenerator.java b/spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassGenerator.java new file mode 100644 index 0000000000..a57f4dd776 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassGenerator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.cglib.core.Transformer; +import org.springframework.asm.ClassVisitor; + +public class TransformingClassGenerator implements ClassGenerator { + private ClassGenerator gen; + private ClassTransformer t; + + public TransformingClassGenerator(ClassGenerator gen, ClassTransformer t) { + this.gen = gen; + this.t = t; + } + + public void generateClass(ClassVisitor v) throws Exception { + t.setTarget(v); + gen.generateClass(t); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassLoader.java b/spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassLoader.java new file mode 100644 index 0000000000..0b1861560c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/TransformingClassLoader.java @@ -0,0 +1,34 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform; + +import java.util.*; +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.asm.*; + +public class TransformingClassLoader extends AbstractClassLoader { + private ClassTransformerFactory t; + + public TransformingClassLoader(ClassLoader parent, ClassFilter filter, ClassTransformerFactory t) { + super(parent, parent, filter); + this.t = t; + } + + protected ClassGenerator getGenerator(ClassReader r) { + ClassTransformer t2 = t.newInstance(); + return new TransformingClassGenerator(super.getGenerator(r), t2); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java new file mode 100644 index 0000000000..3ac5bcc360 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AbstractInterceptFieldCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +/** + * @author Chris Nokleberg + */ +public class AbstractInterceptFieldCallback implements InterceptFieldCallback { + + public int writeInt(Object obj, String name, int oldValue, int newValue) { return newValue; } + public char writeChar(Object obj, String name, char oldValue, char newValue) { return newValue; } + public byte writeByte(Object obj, String name, byte oldValue, byte newValue) { return newValue; } + public boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue) { return newValue; } + public short writeShort(Object obj, String name, short oldValue, short newValue) { return newValue; } + public float writeFloat(Object obj, String name, float oldValue, float newValue) { return newValue; } + public double writeDouble(Object obj, String name, double oldValue, double newValue) { return newValue; } + public long writeLong(Object obj, String name, long oldValue, long newValue) { return newValue; } + public Object writeObject(Object obj, String name, Object oldValue, Object newValue) { return newValue; } + + public int readInt(Object obj, String name, int oldValue) { return oldValue; } + public char readChar(Object obj, String name, char oldValue) { return oldValue; } + public byte readByte(Object obj, String name, byte oldValue) { return oldValue; } + public boolean readBoolean(Object obj, String name, boolean oldValue) { return oldValue; } + public short readShort(Object obj, String name, short oldValue) { return oldValue; } + public float readFloat(Object obj, String name, float oldValue) { return oldValue; } + public double readDouble(Object obj, String name, double oldValue) { return oldValue; } + public long readLong(Object obj, String name, long oldValue) { return oldValue; } + public Object readObject(Object obj, String name, Object oldValue) { return oldValue; } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AccessFieldTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AccessFieldTransformer.java new file mode 100644 index 0000000000..2d322e5c00 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AccessFieldTransformer.java @@ -0,0 +1,64 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.cglib.transform.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; + +public class AccessFieldTransformer extends ClassEmitterTransformer { + private Callback callback; + + public AccessFieldTransformer(Callback callback) { + this.callback = callback; + } + + public interface Callback { + String getPropertyName(Type owner, String fieldName); + } + + public void declare_field(int access, final String name, Type type, Object value) { + super.declare_field(access, name, type, value); + + String property = TypeUtils.upperFirst(callback.getPropertyName(getClassType(), name)); + if (property != null) { + CodeEmitter e; + e = begin_method(Constants.ACC_PUBLIC, + new Signature("get" + property, + type, + Constants.TYPES_EMPTY), + null); + e.load_this(); + e.getfield(name); + e.return_value(); + e.end_method(); + + e = begin_method(Constants.ACC_PUBLIC, + new Signature("set" + property, + Type.VOID_TYPE, + new Type[]{ type }), + null); + e.load_this(); + e.load_arg(0); + e.putfield(name); + e.return_value(); + e.end_method(); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java new file mode 100644 index 0000000000..79097818ad --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddDelegateTransformer.java @@ -0,0 +1,120 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.cglib.transform.*; +import java.lang.reflect.*; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; + +/** + * @author Juozas Baliuka + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class AddDelegateTransformer extends ClassEmitterTransformer { + private static final String DELEGATE = "$CGLIB_DELEGATE"; + private static final Signature CSTRUCT_OBJECT = + TypeUtils.parseSignature("void (Object)"); + + private Class[] delegateIf; + private Class delegateImpl; + private Type delegateType; + + /** Creates a new instance of AddDelegateTransformer */ + public AddDelegateTransformer(Class delegateIf[], Class delegateImpl) { + try { + delegateImpl.getConstructor(new Class[]{ Object.class }); + this.delegateIf = delegateIf; + this.delegateImpl = delegateImpl; + delegateType = Type.getType(delegateImpl); + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + + if(!TypeUtils.isInterface(access)){ + + Type[] all = TypeUtils.add(interfaces, TypeUtils.getTypes(delegateIf)); + super.begin_class(version, access, className, superType, all, sourceFile); + + declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT, + DELEGATE, + delegateType, + null); + for (int i = 0; i < delegateIf.length; i++) { + Method[] methods = delegateIf[i].getMethods(); + for (int j = 0; j < methods.length; j++) { + if (Modifier.isAbstract(methods[j].getModifiers())) { + addDelegate(methods[j]); + } + } + } + }else{ + super.begin_class(version, access, className, superType, interfaces, sourceFile); + } + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + final CodeEmitter e = super.begin_method(access, sig, exceptions); + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) { + return new CodeEmitter(e) { + private boolean transformInit = true; + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + if (transformInit && opcode == Constants.INVOKESPECIAL) { + load_this(); + new_instance(delegateType); + dup(); + load_this(); + invoke_constructor(delegateType, CSTRUCT_OBJECT); + putfield(DELEGATE); + transformInit = false; + } + } + }; + } + return e; + } + + private void addDelegate(Method m) { + Method delegate; + try { + delegate = delegateImpl.getMethod(m.getName(), m.getParameterTypes()); + if (!delegate.getReturnType().getName().equals(m.getReturnType().getName())){ + throw new IllegalArgumentException("Invalid delegate signature " + delegate); + } + } catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + + final Signature sig = ReflectUtils.getSignature(m); + Type[] exceptions = TypeUtils.getTypes(m.getExceptionTypes()); + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, sig, exceptions); + e.load_this(); + e.getfield(DELEGATE); + e.load_args(); + e.invoke_virtual(delegateType, sig); + e.return_value(); + e.end_method(); + } +} + + + diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddInitTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddInitTransformer.java new file mode 100644 index 0000000000..8292b75ef8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddInitTransformer.java @@ -0,0 +1,63 @@ +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import java.lang.reflect.Method; + +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.transform.ClassEmitterTransformer; + +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; + +/** + * @author Mark Hobson + */ +public class AddInitTransformer extends ClassEmitterTransformer { + private MethodInfo info; + + public AddInitTransformer(Method method) { + info = ReflectUtils.getMethodInfo(method); + + Type[] types = info.getSignature().getArgumentTypes(); + if (types.length != 1 || + !types[0].equals(Constants.TYPE_OBJECT) || + !info.getSignature().getReturnType().equals(Type.VOID_TYPE)) { + throw new IllegalArgumentException(method + " illegal signature"); + } + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + final CodeEmitter emitter = super.begin_method(access, sig, exceptions); + if (sig.getName().equals(Constants.CONSTRUCTOR_NAME)) { + return new CodeEmitter(emitter) { + public void visitInsn(int opcode) { + if (opcode == Constants.RETURN) { + load_this(); + invoke(info); + } + super.visitInsn(opcode); + } + }; + } + return emitter; + } +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java new file mode 100644 index 0000000000..7dd0c0db9b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddPropertyTransformer.java @@ -0,0 +1,48 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.cglib.transform.*; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class AddPropertyTransformer extends ClassEmitterTransformer { + private final String[] names; + private final Type[] types; + + public AddPropertyTransformer(Map props) { + int size = props.size(); + names = (String[])props.keySet().toArray(new String[size]); + types = new Type[size]; + for (int i = 0; i < size; i++) { + types[i] = (Type)props.get(names[i]); + } + } + + public AddPropertyTransformer(String[] names, Type[] types) { + this.names = names; + this.types = types; + } + + public void end_class() { + if (!TypeUtils.isAbstract(getAccess())) { + EmitUtils.add_properties(this, names, types); + } + super.end_class(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java new file mode 100644 index 0000000000..6d8ae4cde3 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/AddStaticInitTransformer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import java.lang.reflect.Method; +import org.springframework.cglib.core.*; +import org.springframework.cglib.transform.*; +import org.springframework.asm.Type; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class AddStaticInitTransformer extends ClassEmitterTransformer { + private MethodInfo info; + + public AddStaticInitTransformer(Method classInit) { + info = ReflectUtils.getMethodInfo(classInit); + if (!TypeUtils.isStatic(info.getModifiers())) { + throw new IllegalArgumentException(classInit + " is not static"); + } + Type[] types = info.getSignature().getArgumentTypes(); + if (types.length != 1 || + !types[0].equals(Constants.TYPE_CLASS) || + !info.getSignature().getReturnType().equals(Type.VOID_TYPE)) { + throw new IllegalArgumentException(classInit + " illegal signature"); + } + } + + protected void init() { + if (!TypeUtils.isInterface(getAccess())) { + CodeEmitter e = getStaticHook(); + EmitUtils.load_class_this(e); + e.invoke(info); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java new file mode 100644 index 0000000000..b85294fce7 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public interface FieldProvider { + + String[] getFieldNames(); + + Class[] getFieldTypes(); + + void setField(int index, Object value); + + Object getField(int index); + + void setField(String name, Object value); + + Object getField(String name); + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProviderTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProviderTransformer.java new file mode 100644 index 0000000000..f6d9631bf0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/FieldProviderTransformer.java @@ -0,0 +1,209 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.cglib.transform.*; +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Attribute; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class FieldProviderTransformer extends ClassEmitterTransformer { + + private static final String FIELD_NAMES = "CGLIB$FIELD_NAMES"; + private static final String FIELD_TYPES = "CGLIB$FIELD_TYPES"; + + private static final Type FIELD_PROVIDER = + TypeUtils.parseType("org.springframework.cglib.transform.impl.FieldProvider"); + private static final Type ILLEGAL_ARGUMENT_EXCEPTION = + TypeUtils.parseType("IllegalArgumentException"); + private static final Signature PROVIDER_GET = + TypeUtils.parseSignature("Object getField(String)"); + private static final Signature PROVIDER_SET = + TypeUtils.parseSignature("void setField(String, Object)"); + private static final Signature PROVIDER_SET_BY_INDEX = + TypeUtils.parseSignature("void setField(int, Object)"); + private static final Signature PROVIDER_GET_BY_INDEX = + TypeUtils.parseSignature("Object getField(int)"); + private static final Signature PROVIDER_GET_TYPES = + TypeUtils.parseSignature("Class[] getFieldTypes()"); + private static final Signature PROVIDER_GET_NAMES = + TypeUtils.parseSignature("String[] getFieldNames()"); + + private int access; + private Map fields; + + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + if (!TypeUtils.isAbstract(access)) { + interfaces = TypeUtils.add(interfaces, FIELD_PROVIDER); + } + this.access = access; + fields = new HashMap(); + super.begin_class(version, access, className, superType, interfaces, sourceFile); + } + + public void declare_field(int access, String name, Type type, Object value) { + super.declare_field(access, name, type, value); + + if (!TypeUtils.isStatic(access)) { + fields.put(name, type); + } + } + + public void end_class() { + if (!TypeUtils.isInterface(access)) { + try { + generate(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new CodeGenerationException(e); + } + } + super.end_class(); + } + + private void generate() throws Exception { + final String[] names = (String[])fields.keySet().toArray(new String[fields.size()]); + + int indexes[] = new int[names.length]; + for (int i = 0; i < indexes.length; i++) { + indexes[i] = i; + } + + super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_NAMES, Constants.TYPE_STRING_ARRAY, null); + super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_TYPES, Constants.TYPE_CLASS_ARRAY, null); + + // use separate methods here because each process switch inner class needs a final CodeEmitter + initFieldProvider(names); + getNames(); + getTypes(); + getField(names); + setField(names); + setByIndex(names, indexes); + getByIndex(names, indexes); + } + + private void initFieldProvider(String[] names) { + CodeEmitter e = getStaticHook(); + EmitUtils.push_object(e, names); + e.putstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY); + + e.push(names.length); + e.newarray(Constants.TYPE_CLASS); + e.dup(); + for(int i = 0; i < names.length; i++ ){ + e.dup(); + e.push(i); + Type type = (Type)fields.get(names[i]); + EmitUtils.load_class(e, type); + e.aastore(); + } + e.putstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY); + } + + private void getNames() { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_NAMES, null); + e.getstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY); + e.return_value(); + e.end_method(); + } + + private void getTypes() { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_TYPES, null); + e.getstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY); + e.return_value(); + e.end_method(); + } + + private void setByIndex(final String[] names, final int[] indexes) throws Exception { + final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_SET_BY_INDEX, null); + e.load_this(); + e.load_arg(1); + e.load_arg(0); + e.process_switch(indexes, new ProcessSwitchCallback() { + public void processCase(int key, Label end) throws Exception { + Type type = (Type)fields.get(names[key]); + e.unbox(type); + e.putfield(names[key]); + e.return_value(); + } + public void processDefault() throws Exception { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index"); + } + }); + e.end_method(); + } + + private void getByIndex(final String[] names, final int[] indexes) throws Exception { + final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_BY_INDEX, null); + e.load_this(); + e.load_arg(0); + e.process_switch(indexes, new ProcessSwitchCallback() { + public void processCase(int key, Label end) throws Exception { + Type type = (Type)fields.get(names[key]); + e.getfield(names[key]); + e.box(type); + e.return_value(); + } + public void processDefault() throws Exception { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index"); + } + }); + e.end_method(); + } + + // TODO: if this is used to enhance class files SWITCH_STYLE_TRIE should be used + // to avoid JVM hashcode implementation incompatibilities + private void getField(String[] names) throws Exception { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_GET, null); + e.load_this(); + e.load_arg(0); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + Type type = (Type)fields.get(key); + e.getfield((String)key); + e.box(type); + e.return_value(); + } + public void processDefault() { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name"); + } + }); + e.end_method(); + } + + private void setField(String[] names) throws Exception { + final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_SET, null); + e.load_this(); + e.load_arg(1); + e.load_arg(0); + EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + Type type = (Type)fields.get(key); + e.unbox(type); + e.putfield((String)key); + e.return_value(); + } + public void processDefault() { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name"); + } + }); + e.end_method(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java new file mode 100644 index 0000000000..84225f5c9a --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +/** + * @author Juozas Baliuka + */ +public interface InterceptFieldCallback { + + int writeInt(Object obj, String name, int oldValue, int newValue); + char writeChar(Object obj, String name, char oldValue, char newValue); + byte writeByte(Object obj, String name, byte oldValue, byte newValue); + boolean writeBoolean(Object obj, String name, boolean oldValue, boolean newValue); + short writeShort(Object obj, String name, short oldValue, short newValue); + float writeFloat(Object obj, String name, float oldValue, float newValue); + double writeDouble(Object obj, String name, double oldValue, double newValue); + long writeLong(Object obj, String name, long oldValue, long newValue); + Object writeObject(Object obj, String name, Object oldValue, Object newValue); + + int readInt(Object obj, String name, int oldValue); + char readChar(Object obj, String name, char oldValue); + byte readByte(Object obj, String name, byte oldValue); + boolean readBoolean(Object obj, String name, boolean oldValue); + short readShort(Object obj, String name, short oldValue); + float readFloat(Object obj, String name, float oldValue); + double readDouble(Object obj, String name, double oldValue); + long readLong(Object obj, String name, long oldValue); + Object readObject(Object obj, String name, Object oldValue); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldEnabled.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldEnabled.java new file mode 100644 index 0000000000..14f1a428d0 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldEnabled.java @@ -0,0 +1,21 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +public interface InterceptFieldEnabled { + void setInterceptFieldCallback(InterceptFieldCallback callback); + InterceptFieldCallback getInterceptFieldCallback(); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldFilter.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldFilter.java new file mode 100644 index 0000000000..84deaf2809 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldFilter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.asm.Type; + +public interface InterceptFieldFilter { + boolean acceptRead(Type owner, String name); + boolean acceptWrite(Type owner, String name); +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java new file mode 100644 index 0000000000..2a68dbbb85 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/InterceptFieldTransformer.java @@ -0,0 +1,210 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.cglib.transform.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +/** + * @author Juozas Baliuka, Chris Nokleberg + */ +public class InterceptFieldTransformer extends ClassEmitterTransformer { + private static final String CALLBACK_FIELD = "$CGLIB_READ_WRITE_CALLBACK"; + private static final Type CALLBACK = + TypeUtils.parseType("org.springframework.cglib.transform.impl.InterceptFieldCallback"); + private static final Type ENABLED = + TypeUtils.parseType("org.springframework.cglib.transform.impl.InterceptFieldEnabled"); + private static final Signature ENABLED_SET = + new Signature("setInterceptFieldCallback", Type.VOID_TYPE, new Type[]{ CALLBACK }); + private static final Signature ENABLED_GET = + new Signature("getInterceptFieldCallback", CALLBACK, new Type[0]); + + private InterceptFieldFilter filter; + + public InterceptFieldTransformer(InterceptFieldFilter filter) { + this.filter = filter; + } + + public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) { + if (!TypeUtils.isInterface(access)) { + super.begin_class(version, access, className, superType, TypeUtils.add(interfaces, ENABLED), sourceFile); + + super.declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT, + CALLBACK_FIELD, + CALLBACK, + null); + + CodeEmitter e; + e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_GET, null); + e.load_this(); + e.getfield(CALLBACK_FIELD); + e.return_value(); + e.end_method(); + + e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_SET, null); + e.load_this(); + e.load_arg(0); + e.putfield(CALLBACK_FIELD); + e.return_value(); + e.end_method(); + } else { + super.begin_class(version, access, className, superType, interfaces, sourceFile); + } + } + + public void declare_field(int access, String name, Type type, Object value) { + super.declare_field(access, name, type, value); + if (!TypeUtils.isStatic(access)) { + if (filter.acceptRead(getClassType(), name)) { + addReadMethod(name, type); + } + if (filter.acceptWrite(getClassType(), name)) { + addWriteMethod(name, type); + } + } + } + + private void addReadMethod(String name, Type type) { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, + readMethodSig(name, type.getDescriptor()), + null); + e.load_this(); + e.getfield(name); + e.load_this(); + e.invoke_interface(ENABLED,ENABLED_GET); + Label intercept = e.make_label(); + e.ifnonnull(intercept); + e.return_value(); + + e.mark(intercept); + Local result = e.make_local(type); + e.store_local(result); + e.load_this(); + e.invoke_interface(ENABLED,ENABLED_GET); + e.load_this(); + e.push(name); + e.load_local(result); + e.invoke_interface(CALLBACK, readCallbackSig(type)); + if (!TypeUtils.isPrimitive(type)) { + e.checkcast(type); + } + e.return_value(); + e.end_method(); + } + + private void addWriteMethod(String name, Type type) { + CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, + writeMethodSig(name, type.getDescriptor()), + null); + e.load_this(); + e.dup(); + e.invoke_interface(ENABLED,ENABLED_GET); + Label skip = e.make_label(); + e.ifnull(skip); + + e.load_this(); + e.invoke_interface(ENABLED,ENABLED_GET); + e.load_this(); + e.push(name); + e.load_this(); + e.getfield(name); + e.load_arg(0); + e.invoke_interface(CALLBACK, writeCallbackSig(type)); + if (!TypeUtils.isPrimitive(type)) { + e.checkcast(type); + } + Label go = e.make_label(); + e.goTo(go); + e.mark(skip); + e.load_arg(0); + e.mark(go); + e.putfield(name); + e.return_value(); + e.end_method(); + } + + public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) { + return new CodeEmitter(super.begin_method(access, sig, exceptions)) { + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + Type towner = TypeUtils.fromInternalName(owner); + switch (opcode) { + case Constants.GETFIELD: + if (filter.acceptRead(towner, name)) { + helper(towner, readMethodSig(name, desc)); + return; + } + break; + case Constants.PUTFIELD: + if (filter.acceptWrite(towner, name)) { + helper(towner, writeMethodSig(name, desc)); + return; + } + break; + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + private void helper(Type owner, Signature sig) { + invoke_virtual(owner, sig); + } + }; + } + + private static Signature readMethodSig(String name, String desc) { + return new Signature("$cglib_read_" + name, "()" + desc); + } + + private static Signature writeMethodSig(String name, String desc) { + return new Signature("$cglib_write_" + name, "(" + desc + ")V"); + } + + private static Signature readCallbackSig(Type type) { + Type remap = remap(type); + return new Signature("read" + callbackName(remap), + remap, + new Type[]{ Constants.TYPE_OBJECT, + Constants.TYPE_STRING, + remap }); + } + + private static Signature writeCallbackSig(Type type) { + Type remap = remap(type); + return new Signature("write" + callbackName(remap), + remap, + new Type[]{ Constants.TYPE_OBJECT, + Constants.TYPE_STRING, + remap, + remap }); + } + + private static Type remap(Type type) { + switch (type.getSort()) { + case Type.OBJECT: + case Type.ARRAY: + return Constants.TYPE_OBJECT; + default: + return type; + } + } + + private static String callbackName(Type type) { + return (type == Constants.TYPE_OBJECT) ? + "Object" : + TypeUtils.upperFirst(TypeUtils.getClassName(type)); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java new file mode 100644 index 0000000000..4e18cefce4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableStrategy.java @@ -0,0 +1,61 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import org.springframework.cglib.core.ClassGenerator; +import org.springframework.cglib.core.DefaultGeneratorStrategy; +import org.springframework.cglib.core.GeneratorStrategy; +import org.springframework.cglib.core.TypeUtils; +import org.springframework.cglib.transform.ClassTransformer; +import org.springframework.cglib.transform.MethodFilter; +import org.springframework.cglib.transform.MethodFilterTransformer; +import org.springframework.cglib.transform.TransformingClassGenerator; + +/** + * A {@link GeneratorStrategy} suitable for use with {@link org.springframework.cglib.Enhancer} which + * causes all undeclared exceptions thrown from within a proxied method to be wrapped + * in an alternative exception of your choice. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class UndeclaredThrowableStrategy extends DefaultGeneratorStrategy { + + private Class wrapper; + + /** + * Create a new instance of this strategy. + * @param wrapper a class which extends either directly or + * indirectly from Throwable and which has at least one + * constructor that takes a single argument of type + * Throwable, for example + * java.lang.reflect.UndeclaredThrowableException.class + */ + public UndeclaredThrowableStrategy(Class wrapper) { + this.wrapper = wrapper; + } + + private static final MethodFilter TRANSFORM_FILTER = new MethodFilter() { + public boolean accept(int access, String name, String desc, String signature, String[] exceptions) { + return !TypeUtils.isPrivate(access) && name.indexOf('$') < 0; + } + }; + + protected ClassGenerator transform(ClassGenerator cg) throws Exception { + ClassTransformer tr = new UndeclaredThrowableTransformer(wrapper); + tr = new MethodFilterTransformer(TRANSFORM_FILTER, tr); + return new TransformingClassGenerator(cg, tr); + } +} + diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java new file mode 100644 index 0000000000..8415ac5958 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java @@ -0,0 +1,61 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.transform.impl; + +import java.lang.reflect.Constructor; +import org.springframework.cglib.core.*; +import org.springframework.cglib.transform.*; +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; +import org.springframework.asm.ClassVisitor; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class UndeclaredThrowableTransformer extends ClassEmitterTransformer { + private Type wrapper; + + public UndeclaredThrowableTransformer(Class wrapper) { + this.wrapper = Type.getType(wrapper); + boolean found = false; + Constructor[] cstructs = wrapper.getConstructors(); + for (int i = 0; i < cstructs.length; i++) { + Class[] types = cstructs[i].getParameterTypes(); + if (types.length == 1 && types[0].equals(Throwable.class)) { + found = true; + break; + } + } + if (!found) + throw new IllegalArgumentException(wrapper + " does not have a single-arg constructor that takes a Throwable"); + } + + public CodeEmitter begin_method(int access, final Signature sig, final Type[] exceptions) { + CodeEmitter e = super.begin_method(access, sig, exceptions); + if (TypeUtils.isAbstract(access) || sig.equals(Constants.SIG_STATIC)) { + return e; + } + return new CodeEmitter(e) { + private Block handler; + /* init */ { + handler = begin_block(); + } + public void visitMaxs(int maxStack, int maxLocals) { + handler.end(); + EmitUtils.wrap_undeclared_throwable(this, handler, exceptions, wrapper); + super.visitMaxs(maxStack, maxLocals); + } + }; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java b/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java new file mode 100644 index 0000000000..7f9821add8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorter.java @@ -0,0 +1,295 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.util; + +import java.lang.reflect.*; +import java.util.Comparator; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; + +/** + * For the efficient sorting of multiple arrays in parallel. + *

+ * Given two arrays of equal length and varying types, the standard + * technique for sorting them in parallel is to create a new temporary + * object for each row, store the objects in a temporary array, sort the + * array using a custom comparator, and the extract the original values + * back into their respective arrays. This is wasteful in both time and + * memory. + *

+ * This class generates bytecode customized to the particular set of + * arrays you need to sort, in such a way that both arrays are sorted + * in-place, simultaneously. + *

+ * Two sorting algorithms are provided. + * Quicksort is best when you only need to sort by a single column, as + * it requires very few comparisons and swaps. Mergesort is best used + * when sorting multiple columns, as it is a "stable" sort--that is, it + * does not affect the relative order of equal objects from previous sorts. + *

+ * The mergesort algorithm here is an "in-place" variant, which while + * slower, does not require a temporary array. + * + * @author Chris Nokleberg + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class ParallelSorter extends SorterTemplate { + protected Object[] a; + private Comparer comparer; + + protected ParallelSorter() { + } + + abstract public ParallelSorter newInstance(Object[] arrays); + + /** + * Create a new ParallelSorter object for a set of arrays. You may + * sort the arrays multiple times via the same ParallelSorter object. + * @param arrays An array of arrays to sort. The arrays may be a mix + * of primitive and non-primitive types, but should all be the same + * length. + * @param loader ClassLoader for generated class, uses "current" if null + */ + public static ParallelSorter create(Object[] arrays) { + Generator gen = new Generator(); + gen.setArrays(arrays); + return gen.create(); + } + + private int len() { + return ((Object[])a[0]).length; + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + */ + public void quickSort(int index) { + quickSort(index, 0, len(), null); + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + */ + public void quickSort(int index, int lo, int hi) { + quickSort(index, lo, hi, null); + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + * @param cmp Comparator to use if the specified column is non-primitive + */ + public void quickSort(int index, Comparator cmp) { + quickSort(index, 0, len(), cmp); + } + + /** + * Sort the arrays using the quicksort algorithm. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + * @param cmp Comparator to use if the specified column is non-primitive + */ + public void quickSort(int index, int lo, int hi, Comparator cmp) { + chooseComparer(index, cmp); + super.quickSort(lo, hi - 1); + } + + /** + * @param index array (column) to sort by + */ + public void mergeSort(int index) { + mergeSort(index, 0, len(), null); + } + + /** + * Sort the arrays using an in-place merge sort. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + */ + public void mergeSort(int index, int lo, int hi) { + mergeSort(index, lo, hi, null); + } + + /** + * Sort the arrays using an in-place merge sort. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + */ + public void mergeSort(int index, Comparator cmp) { + mergeSort(index, 0, len(), cmp); + } + + /** + * Sort the arrays using an in-place merge sort. + * @param index array (column) to sort by + * @param lo starting array index (row), inclusive + * @param hi ending array index (row), exclusive + * @param cmp Comparator to use if the specified column is non-primitive + */ + public void mergeSort(int index, int lo, int hi, Comparator cmp) { + chooseComparer(index, cmp); + super.mergeSort(lo, hi - 1); + } + + private void chooseComparer(int index, Comparator cmp) { + Object array = a[index]; + Class type = array.getClass().getComponentType(); + if (type.equals(Integer.TYPE)) { + comparer = new IntComparer((int[])array); + } else if (type.equals(Long.TYPE)) { + comparer = new LongComparer((long[])array); + } else if (type.equals(Double.TYPE)) { + comparer = new DoubleComparer((double[])array); + } else if (type.equals(Float.TYPE)) { + comparer = new FloatComparer((float[])array); + } else if (type.equals(Short.TYPE)) { + comparer = new ShortComparer((short[])array); + } else if (type.equals(Byte.TYPE)) { + comparer = new ByteComparer((byte[])array); + } else if (cmp != null) { + comparer = new ComparatorComparer((Object[])array, cmp); + } else { + comparer = new ObjectComparer((Object[])array); + } + } + + protected int compare(int i, int j) { + return comparer.compare(i, j); + } + + interface Comparer { + int compare(int i, int j); + } + + static class ComparatorComparer implements Comparer { + private Object[] a; + private Comparator cmp; + + public ComparatorComparer(Object[] a, Comparator cmp) { + this.a = a; + this.cmp = cmp; + } + + public int compare(int i, int j) { + return cmp.compare(a[i], a[j]); + } + } + + static class ObjectComparer implements Comparer { + private Object[] a; + public ObjectComparer(Object[] a) { this.a = a; } + public int compare(int i, int j) { + return ((Comparable)a[i]).compareTo(a[j]); + } + } + + static class IntComparer implements Comparer { + private int[] a; + public IntComparer(int[] a) { this.a = a; } + public int compare(int i, int j) { return a[i] - a[j]; } + } + + static class LongComparer implements Comparer { + private long[] a; + public LongComparer(long[] a) { this.a = a; } + public int compare(int i, int j) { + long vi = a[i]; + long vj = a[j]; + return (vi == vj) ? 0 : (vi > vj) ? 1 : -1; + } + } + + static class FloatComparer implements Comparer { + private float[] a; + public FloatComparer(float[] a) { this.a = a; } + public int compare(int i, int j) { + float vi = a[i]; + float vj = a[j]; + return (vi == vj) ? 0 : (vi > vj) ? 1 : -1; + } + } + + static class DoubleComparer implements Comparer { + private double[] a; + public DoubleComparer(double[] a) { this.a = a; } + public int compare(int i, int j) { + double vi = a[i]; + double vj = a[j]; + return (vi == vj) ? 0 : (vi > vj) ? 1 : -1; + } + } + + static class ShortComparer implements Comparer { + private short[] a; + public ShortComparer(short[] a) { this.a = a; } + public int compare(int i, int j) { return a[i] - a[j]; } + } + + static class ByteComparer implements Comparer { + private byte[] a; + public ByteComparer(byte[] a) { this.a = a; } + public int compare(int i, int j) { return a[i] - a[j]; } + } + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(ParallelSorter.class.getName()); + + private Object[] arrays; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return null; // TODO + } + + public void setArrays(Object[] arrays) { + this.arrays = arrays; + } + + public ParallelSorter create() { + return (ParallelSorter)super.create(ClassesKey.create(arrays)); + } + + public void generateClass(ClassVisitor v) throws Exception { + if (arrays.length == 0) { + throw new IllegalArgumentException("No arrays specified to sort"); + } + for (int i = 0; i < arrays.length; i++) { + if (!arrays[i].getClass().isArray()) { + throw new IllegalArgumentException(arrays[i].getClass() + " is not an array"); + } + } + new ParallelSorterEmitter(v, getClassName(), arrays); + } + + protected Object firstInstance(Class type) { + return ((ParallelSorter)ReflectUtils.newInstance(type)).newInstance(arrays); + } + + protected Object nextInstance(Object instance) { + return ((ParallelSorter)instance).newInstance(arrays); + } + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorterEmitter.java b/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorterEmitter.java new file mode 100644 index 0000000000..298c0f234f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/util/ParallelSorterEmitter.java @@ -0,0 +1,100 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.util; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Type; + +class ParallelSorterEmitter extends ClassEmitter { + private static final Type PARALLEL_SORTER = + TypeUtils.parseType("org.springframework.cglib.util.ParallelSorter"); + private static final Signature CSTRUCT_OBJECT_ARRAY = + TypeUtils.parseConstructor("Object[]"); + private static final Signature NEW_INSTANCE = + new Signature("newInstance", PARALLEL_SORTER, new Type[]{ Constants.TYPE_OBJECT_ARRAY }); + private static final Signature SWAP = + TypeUtils.parseSignature("void swap(int, int)"); + + public ParallelSorterEmitter(ClassVisitor v, String className, Object[] arrays) { + super(v); + begin_class(Constants.V1_8, Constants.ACC_PUBLIC, className, PARALLEL_SORTER, null, Constants.SOURCE_FILE); + EmitUtils.null_constructor(this); + EmitUtils.factory_method(this, NEW_INSTANCE); + generateConstructor(arrays); + generateSwap(arrays); + end_class(); + } + + private String getFieldName(int index) { + return "FIELD_" + index; + } + + private void generateConstructor(Object[] arrays) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT_ARRAY, null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + e.load_arg(0); + e.super_putfield("a", Constants.TYPE_OBJECT_ARRAY); + for (int i = 0; i < arrays.length; i++) { + Type type = Type.getType(arrays[i].getClass()); + declare_field(Constants.ACC_PRIVATE, getFieldName(i), type, null); + e.load_this(); + e.load_arg(0); + e.push(i); + e.aaload(); + e.checkcast(type); + e.putfield(getFieldName(i)); + } + e.return_value(); + e.end_method(); + } + + private void generateSwap(final Object[] arrays) { + CodeEmitter e = begin_method(Constants.ACC_PUBLIC, SWAP, null); + for (int i = 0; i < arrays.length; i++) { + Type type = Type.getType(arrays[i].getClass()); + Type component = TypeUtils.getComponentType(type); + Local T = e.make_local(type); + + e.load_this(); + e.getfield(getFieldName(i)); + e.store_local(T); + + e.load_local(T); + e.load_arg(0); + + e.load_local(T); + e.load_arg(1); + e.array_load(component); + + e.load_local(T); + e.load_arg(1); + + e.load_local(T); + e.load_arg(0); + e.array_load(component); + + e.array_store(component); + e.array_store(component); + } + e.return_value(); + e.end_method(); + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java b/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java new file mode 100644 index 0000000000..10f33495ad --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/util/SorterTemplate.java @@ -0,0 +1,173 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.util; + +import java.util.*; + +abstract class SorterTemplate { + private static final int MERGESORT_THRESHOLD = 12; + private static final int QUICKSORT_THRESHOLD = 7; + + abstract protected void swap(int i, int j); + abstract protected int compare(int i, int j); + + protected void quickSort(int lo, int hi) { + quickSortHelper(lo, hi); + insertionSort(lo, hi); + } + + private void quickSortHelper(int lo, int hi) { + for (;;) { + int diff = hi - lo; + if (diff <= QUICKSORT_THRESHOLD) { + break; + } + int i = (hi + lo) / 2; + if (compare(lo, i) > 0) { + swap(lo, i); + } + if (compare(lo, hi) > 0) { + swap(lo, hi); + } + if (compare(i, hi) > 0) { + swap(i, hi); + } + int j = hi - 1; + swap(i, j); + i = lo; + int v = j; + for (;;) { + while (compare(++i, v) < 0) { + /* nothing */; + } + while (compare(--j, v) > 0) { + /* nothing */; + } + if (j < i) { + break; + } + swap(i, j); + } + swap(i, hi - 1); + if (j - lo <= hi - i + 1) { + quickSortHelper(lo, j); + lo = i + 1; + } else { + quickSortHelper(i + 1, hi); + hi = j; + } + } + } + + private void insertionSort(int lo, int hi) { + for (int i = lo + 1 ; i <= hi; i++) { + for (int j = i; j > lo; j--) { + if (compare(j - 1, j) > 0) { + swap(j - 1, j); + } else { + break; + } + } + } + } + + protected void mergeSort(int lo, int hi) { + int diff = hi - lo; + if (diff <= MERGESORT_THRESHOLD) { + insertionSort(lo, hi); + return; + } + int mid = lo + diff / 2; + mergeSort(lo, mid); + mergeSort(mid, hi); + merge(lo, mid, hi, mid - lo, hi - mid); + } + + private void merge(int lo, int pivot, int hi, int len1, int len2) { + if (len1 == 0 || len2 == 0) { + return; + } + if (len1 + len2 == 2) { + if (compare(pivot, lo) < 0) { + swap(pivot, lo); + } + return; + } + int first_cut, second_cut; + int len11, len22; + if (len1 > len2) { + len11 = len1 / 2; + first_cut = lo + len11; + second_cut = lower(pivot, hi, first_cut); + len22 = second_cut - pivot; + } else { + len22 = len2 / 2; + second_cut = pivot + len22; + first_cut = upper(lo, pivot, second_cut); + len11 = first_cut - lo; + } + rotate(first_cut, pivot, second_cut); + int new_mid = first_cut + len22; + merge(lo, first_cut, new_mid, len11, len22); + merge(new_mid, second_cut, hi, len1 - len11, len2 - len22); + } + + private void rotate(int lo, int mid, int hi) { + int lot = lo; + int hit = mid - 1; + while (lot < hit) { + swap(lot++, hit--); + } + lot = mid; hit = hi - 1; + while (lot < hit) { + swap(lot++, hit--); + } + lot = lo; hit = hi - 1; + while (lot < hit) { + swap(lot++, hit--); + } + } + + private int lower(int lo, int hi, int val) { + int len = hi - lo; + while (len > 0) { + int half = len / 2; + int mid= lo + half; + if (compare(mid, val) < 0) { + lo = mid + 1; + len = len - half -1; + } else { + len = half; + } + } + return lo; + } + + private int upper(int lo, int hi, int val) { + int len = hi - lo; + while (len > 0) { + int half = len / 2; + int mid = lo + half; + if (compare(val, mid) < 0) { + len = half; + } else { + lo = mid + 1; + len = len - half -1; + } + } + return lo; + } +} diff --git a/spring-core/src/main/java/org/springframework/cglib/util/StringSwitcher.java b/spring-core/src/main/java/org/springframework/cglib/util/StringSwitcher.java new file mode 100644 index 0000000000..3a35ead05f --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/util/StringSwitcher.java @@ -0,0 +1,155 @@ +/* + * Copyright 2003 The Apache Software Foundation + * + * 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 + * + * https://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. + */ +package org.springframework.cglib.util; + +import java.util.*; +import org.springframework.cglib.core.*; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Label; +import org.springframework.asm.Type; + +/** + * This class implements a simple String->int mapping for a fixed set of keys. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class StringSwitcher { + private static final Type STRING_SWITCHER = + TypeUtils.parseType("org.springframework.cglib.util.StringSwitcher"); + private static final Signature INT_VALUE = + TypeUtils.parseSignature("int intValue(String)"); + private static final StringSwitcherKey KEY_FACTORY = + (StringSwitcherKey)KeyFactory.create(StringSwitcherKey.class); + + interface StringSwitcherKey { + public Object newInstance(String[] strings, int[] ints, boolean fixedInput); + } + + /** + * Helper method to create a StringSwitcher. + * For finer control over the generated instance, use a new instance of StringSwitcher.Generator + * instead of this static method. + * @param strings the array of String keys; must be the same length as the value array + * @param ints the array of integer results; must be the same length as the key array + * @param fixedInput if false, an unknown key will be returned from {@link #intValue} as -1; if true, + * the result will be undefined, and the resulting code will be faster + */ + public static StringSwitcher create(String[] strings, int[] ints, boolean fixedInput) { + Generator gen = new Generator(); + gen.setStrings(strings); + gen.setInts(ints); + gen.setFixedInput(fixedInput); + return gen.create(); + } + + protected StringSwitcher() { + } + + /** + * Return the integer associated with the given key. + * @param s the key + * @return the associated integer value, or -1 if the key is unknown (unless + * fixedInput was specified when this StringSwitcher was created, + * in which case the return value for an unknown key is undefined) + */ + abstract public int intValue(String s); + + public static class Generator extends AbstractClassGenerator { + private static final Source SOURCE = new Source(StringSwitcher.class.getName()); + + private String[] strings; + private int[] ints; + private boolean fixedInput; + + public Generator() { + super(SOURCE); + } + + /** + * Set the array of recognized Strings. + * @param strings the array of String keys; must be the same length as the value array + * @see #setInts + */ + public void setStrings(String[] strings) { + this.strings = strings; + } + + /** + * Set the array of integer results. + * @param ints the array of integer results; must be the same length as the key array + * @see #setStrings + */ + public void setInts(int[] ints) { + this.ints = ints; + } + + /** + * Configure how unknown String keys will be handled. + * @param fixedInput if false, an unknown key will be returned from {@link #intValue} as -1; if true, + * the result will be undefined, and the resulting code will be faster + */ + public void setFixedInput(boolean fixedInput) { + this.fixedInput = fixedInput; + } + + protected ClassLoader getDefaultClassLoader() { + return getClass().getClassLoader(); + } + + /** + * Generate the StringSwitcher. + */ + public StringSwitcher create() { + setNamePrefix(StringSwitcher.class.getName()); + Object key = KEY_FACTORY.newInstance(strings, ints, fixedInput); + return (StringSwitcher)super.create(key); + } + + public void generateClass(ClassVisitor v) throws Exception { + ClassEmitter ce = new ClassEmitter(v); + ce.begin_class(Constants.V1_8, + Constants.ACC_PUBLIC, + getClassName(), + STRING_SWITCHER, + null, + Constants.SOURCE_FILE); + EmitUtils.null_constructor(ce); + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, INT_VALUE, null); + e.load_arg(0); + final List stringList = Arrays.asList(strings); + int style = fixedInput ? Constants.SWITCH_STYLE_HASHONLY : Constants.SWITCH_STYLE_HASH; + EmitUtils.string_switch(e, strings, style, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + e.push(ints[stringList.indexOf(key)]); + e.return_value(); + } + public void processDefault() { + e.push(-1); + e.return_value(); + } + }); + e.end_method(); + ce.end_class(); + } + + protected Object firstInstance(Class type) { + return (StringSwitcher)ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + } +}