Skip to content

Commit 3572346

Browse files
committed
Use ASM 5.x and jarjar it within core
1 parent 1a493d0 commit 3572346

File tree

20 files changed

+388
-163
lines changed

20 files changed

+388
-163
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ build/
55
classes/
66
out/
77
*.db
8+
*.log

aop/build.gradle

+32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
import org.anarres.gradle.plugin.jarjar.JarjarTask
2+
13
dependencies {
24
compile project(":inject")
5+
}
6+
7+
def targetDir = new File(project.buildDir, "jarjar")
8+
def targetFile = new File(targetDir, "asm.jar")
9+
def jarjarTask = task("jarjarTask", type: JarjarTask) {
10+
from(jar.outputs.files)
11+
classRename "org.objectweb.asm.**", "org.particleframework.asm.@1"
12+
destinationDir = targetDir
13+
destinationName = targetFile.name
14+
}
15+
jarjarTask.inputs.files(jar.outputs)
16+
jarjarTask.outputs.files(targetFile)
17+
18+
def jarjarOutputs = task("jarjarOutputs").doLast {
19+
def jar = jar.outputs.files.first()
20+
21+
copy {
22+
from(targetFile).into(jar.parentFile)
23+
rename { String fileName ->
24+
jar.name
25+
}
26+
}
27+
delete(targetDir)
28+
}
29+
jarjarOutputs.dependsOn(jarjarTask)
30+
jarjarOutputs.inputs.files(targetFile)
31+
jarjarOutputs.outputs.files(jar.outputs)
32+
33+
tasks.withType(AbstractPublishToMaven) {
34+
it.finalizedBy(jarjarOutputs)
335
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright 2017 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.particleframework.aop.writer;
17+
18+
import org.objectweb.asm.ClassWriter;
19+
import org.objectweb.asm.MethodVisitor;
20+
import org.objectweb.asm.Opcodes;
21+
import org.objectweb.asm.Type;
22+
import org.objectweb.asm.commons.GeneratorAdapter;
23+
import org.objectweb.asm.commons.Method;
24+
import org.particleframework.aop.Intercepted;
25+
import org.particleframework.aop.Interceptor;
26+
import org.particleframework.inject.writer.AbstractClassFileWriter;
27+
import org.particleframework.inject.writer.BeanDefinitionWriter;
28+
import org.particleframework.inject.writer.ClassGenerationException;
29+
30+
import java.io.File;
31+
import java.io.OutputStream;
32+
import java.util.*;
33+
34+
import static org.particleframework.inject.writer.BeanDefinitionWriter.DEFAULT_MAX_STACK;
35+
36+
/**
37+
* A class that generates AOP classes at compile time
38+
*
39+
* @author Graeme Rocher
40+
* @since 1.0
41+
*/
42+
public class AopProxyWriter extends AbstractClassFileWriter {
43+
private final String packageName;
44+
private final String targetClassShortName;
45+
private final ClassWriter classWriter;
46+
private final String targetClassFullName;
47+
private final String proxyFullName;
48+
private final BeanDefinitionWriter proxyBeanDefinitionWriter;
49+
private final String proxyInternalName;
50+
private final Object[] interceptorTypes;
51+
52+
private MethodVisitor constructorWriter;
53+
54+
public AopProxyWriter(String packageName, String targetClassShortName, Object... interceptorTypes) {
55+
this.packageName = packageName;
56+
this.targetClassShortName = targetClassShortName;
57+
this.targetClassFullName = packageName + '.' + targetClassShortName;
58+
this.classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
59+
String proxyShortName = targetClassShortName + "$Intercepted";
60+
this.proxyFullName = packageName + '.' + proxyShortName;
61+
this.proxyInternalName = getInternalName(this.proxyFullName);
62+
this.interceptorTypes = interceptorTypes;
63+
// TODO: propagate scopes
64+
this.proxyBeanDefinitionWriter = new BeanDefinitionWriter(packageName, proxyShortName, null, true);
65+
startClass(classWriter, proxyFullName, getTypeReference(targetClassFullName));
66+
}
67+
68+
/**
69+
* @return The bean definition writer for this proxy
70+
*/
71+
public BeanDefinitionWriter getProxyBeanDefinitionWriter() {
72+
return proxyBeanDefinitionWriter;
73+
}
74+
75+
/**
76+
* Visits a no arguments constructor. Either this method or {@link #visitConstructor(Map, Map, Map)} should be called at least once
77+
*/
78+
public void visitConstructor() {
79+
visitConstructor(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
80+
}
81+
82+
/**
83+
* Visits a constructor with arguments. Either this method or {@link #visitConstructor()} should be called at least once
84+
*
85+
* @param argumentTypes The argument names and types. Should be an ordered map should as {@link LinkedHashMap}
86+
* @param qualifierTypes The argument names and qualifier types. Should be an ordered map should as {@link LinkedHashMap}
87+
* @param genericTypes The argument names and generic types. Should be an ordered map should as {@link LinkedHashMap}
88+
*/
89+
public void visitConstructor(Map<String, Object> argumentTypes, Map<String, Object> qualifierTypes, Map<String, List<Object>> genericTypes) {
90+
Map<String,Object> newArgumentTypes = new LinkedHashMap<>(argumentTypes);
91+
newArgumentTypes.put("interceptors", Interceptor[].class);
92+
93+
String constructorDescriptor = getConstructorDescriptor(newArgumentTypes.values());
94+
this.constructorWriter = classWriter.visitMethod(ACC_PUBLIC, "<init>", constructorDescriptor, null, null);
95+
GeneratorAdapter adapter = new GeneratorAdapter(constructorWriter, Opcodes.ACC_PUBLIC, "<init>", constructorDescriptor);
96+
adapter.loadThis();
97+
Collection<Object> existingArguments = argumentTypes.values();
98+
for (int i = 0; i < existingArguments.size(); i++) {
99+
adapter.loadArg(i);
100+
}
101+
String superConstructorDescriptor = getConstructorDescriptor(existingArguments);
102+
adapter.invokeConstructor(getTypeReference(targetClassFullName), new Method("<init>", superConstructorDescriptor));
103+
proxyBeanDefinitionWriter.visitBeanDefinitionConstructor(newArgumentTypes, qualifierTypes, genericTypes);
104+
}
105+
106+
public void visitProxyEnd() {
107+
classWriter.visit(V1_8, ACC_PUBLIC,
108+
proxyInternalName,
109+
null,
110+
getTypeReference(targetClassFullName).getInternalName(),
111+
new String[]{Type.getInternalName(Intercepted.class)});
112+
113+
if(constructorWriter == null) {
114+
throw new IllegalStateException("The method visitConstructor(..) should be called at least once");
115+
}
116+
117+
constructorWriter.visitInsn(RETURN);
118+
constructorWriter.visitMaxs(DEFAULT_MAX_STACK, 1);
119+
120+
this.constructorWriter.visitEnd();
121+
proxyBeanDefinitionWriter.visitBeanDefinitionEnd();
122+
classWriter.visitEnd();
123+
}
124+
/**
125+
* Write the class to the target directory
126+
*
127+
* @param targetDir The target directory
128+
*/
129+
public void writeTo(File targetDir) {
130+
try {
131+
132+
writeClassToDisk(targetDir, classWriter, proxyInternalName);
133+
134+
} catch (Throwable e) {
135+
throw new ClassGenerationException("Error generating proxy definition class for bean definition ["+targetClassFullName+"]: " + e.getMessage(), e);
136+
}
137+
}
138+
139+
/**
140+
* Write the class to the output stream, such a JavaFileObject created from a java annotation processor Filer object
141+
*
142+
* @param outputStream the output stream pointing to the target class file
143+
*/
144+
public void writeTo(OutputStream outputStream) {
145+
try {
146+
writeClassToDisk(outputStream, classWriter);
147+
148+
} catch (Throwable e) {
149+
throw new ClassGenerationException("Error generating bean definition class for bean definition ["+targetClassFullName+"]: " + e.getMessage(), e);
150+
}
151+
}
152+
153+
}

ast/build.gradle

+5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ configurations.all {
33
}
44
dependencies {
55
compile project(":inject")
6+
67
compileOnly project(":aop")
8+
compileOnly "org.ow2.asm:asm:$asmVersion"
9+
compileOnly "org.ow2.asm:asm-commons:$asmVersion"
10+
11+
712
testCompile project(":aop")
813
testCompile "junit:junit:4.7"
914
// testCompile 'javax.validation:validation-api:1.1.0.Final'

ast/src/main/groovy/org/particleframework/ast/groovy/InjectTransform.groovy

+57-21
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.codehaus.groovy.control.SourceUnit
1212
import org.codehaus.groovy.transform.ASTTransformation
1313
import org.codehaus.groovy.transform.GroovyASTTransformation
1414
import org.particleframework.aop.Around
15+
import org.particleframework.aop.writer.AopProxyWriter
1516
import org.particleframework.ast.groovy.annotation.AnnotationStereoTypeFinder
1617
import org.particleframework.ast.groovy.utils.AstAnnotationUtils
1718
import org.particleframework.ast.groovy.utils.AstGenericUtils
@@ -34,6 +35,7 @@ import java.lang.reflect.Modifier
3435

3536
import static org.codehaus.groovy.ast.ClassHelper.make
3637
import static org.codehaus.groovy.ast.ClassHelper.makeCached
38+
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX
3739
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
3840
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName
3941

@@ -218,7 +220,7 @@ class InjectTransform implements ASTTransformation, CompilationUnitAware {
218220
}
219221
beanDefinitionWriters.put(methodNode, beanMethodWriter)
220222
}
221-
else if (stereoTypeFinder.hasStereoType(methodNode, Inject.name, PostConstruct.name, PreDestroy.name, "org.particleframework.aop.Around")) {
223+
else if (stereoTypeFinder.hasStereoType(methodNode, Inject.name, PostConstruct.name, PreDestroy.name)) {
222224
defineBeanDefinition(concreteClass)
223225

224226

@@ -276,18 +278,6 @@ class InjectTransform implements ASTTransformation, CompilationUnitAware {
276278
paramsToType,
277279
qualifierTypes,
278280
genericTypeMap)
279-
} else if (stereoTypeFinder.hasStereoType(methodNode, "org.particleframework.aop.Around")) {
280-
AnnotationNode[] annotations = stereoTypeFinder.findAnnotationsWithStereoType(methodNode, Around)
281-
Object[] annotationTypeReferences = resolveTypeReferences(annotations)
282-
beanWriter.visitMethodProxy(
283-
annotationTypeReferences,
284-
resolveTypeReference(declaringClass),
285-
requiresReflection,
286-
resolveTypeReference(methodNode.returnType),
287-
methodNode.name,
288-
paramsToType,
289-
qualifierTypes,
290-
genericTypeMap)
291281
} else {
292282
beanWriter.visitMethodInjectionPoint(
293283
resolveTypeReference(declaringClass),
@@ -324,6 +314,47 @@ class InjectTransform implements ASTTransformation, CompilationUnitAware {
324314
paramsToType,
325315
qualifierTypes,
326316
genericTypeMap)
317+
318+
if(stereoTypeFinder.hasStereoType(methodNode, "org.particleframework.aop.Around")) {
319+
320+
AnnotationNode[] annotations = stereoTypeFinder.findAnnotationsWithStereoType(methodNode, Around)
321+
Object[] annotationTypeReferences = resolveTypeReferences(annotations)
322+
AopProxyWriter aopProxyWriter = new AopProxyWriter(concreteClass.packageName, concreteClass.nameWithoutPackage, annotationTypeReferences)
323+
324+
List<ConstructorNode> constructors = concreteClass.getDeclaredConstructors()
325+
if(constructors.isEmpty()) {
326+
aopProxyWriter.visitConstructor()
327+
}
328+
else {
329+
ConstructorNode constructorNode = findConcreteConstructor(constructors)
330+
331+
if (constructorNode != null) {
332+
Map<String, Object> constructorParamsToType = [:]
333+
Map<String, Object> constructorQualifierTypes = [:]
334+
Map<String, List<Object>> constructorGenericTypeMap = [:]
335+
Parameter[] parameters = constructorNode.parameters
336+
populateParameterData(parameters,
337+
constructorParamsToType,
338+
constructorQualifierTypes,
339+
constructorGenericTypeMap)
340+
aopProxyWriter.visitConstructor( constructorParamsToType, constructorQualifierTypes, constructorGenericTypeMap)
341+
aopProxyWriter.visitProxyEnd()
342+
343+
} else {
344+
addError("Class must have at least one public constructor in order to be a candidate for dependency injection", concreteClass)
345+
return
346+
}
347+
348+
349+
}
350+
aopProxyWriter.writeTo(sourceUnit.configuration.targetDirectory)
351+
def node = new AnnotatedNode()
352+
def replaces = new AnnotationNode(makeCached(Replaces))
353+
replaces.setMember('value', classX(concreteClass.plainNodeReference))
354+
node.addAnnotation(replaces)
355+
beanDefinitionWriters.put(node, aopProxyWriter.getProxyBeanDefinitionWriter())
356+
357+
}
327358
}
328359
}
329360
}
@@ -513,14 +544,7 @@ class InjectTransform implements ASTTransformation, CompilationUnitAware {
513544
beanWriter.visitBeanDefinitionConstructor(Collections.emptyMap(), null, null)
514545

515546
} else {
516-
List<ConstructorNode> publicConstructors = findPublicConstructors(constructors)
517-
518-
ConstructorNode constructorNode
519-
if (publicConstructors.size() == 1) {
520-
constructorNode = publicConstructors[0]
521-
} else {
522-
constructorNode = publicConstructors.find() { it.getAnnotations(makeCached(Inject)) }
523-
}
547+
ConstructorNode constructorNode = findConcreteConstructor(constructors)
524548
if (constructorNode != null) {
525549
Map<String, Object> paramsToType = [:]
526550
Map<String, Object> qualifierTypes = [:]
@@ -553,6 +577,18 @@ class InjectTransform implements ASTTransformation, CompilationUnitAware {
553577
}
554578
}
555579

580+
private ConstructorNode findConcreteConstructor(List<ConstructorNode> constructors) {
581+
List<ConstructorNode> publicConstructors = findPublicConstructors(constructors)
582+
583+
ConstructorNode constructorNode
584+
if (publicConstructors.size() == 1) {
585+
constructorNode = publicConstructors[0]
586+
} else {
587+
constructorNode = publicConstructors.find() { it.getAnnotations(makeCached(Inject)) }
588+
}
589+
constructorNode
590+
}
591+
556592
private void populateParameterData(Parameter[] parameters, Map<String, Object> paramsToType, Map<String, Object> qualifierTypes, Map<String, List<Object>> genericTypeMap) {
557593
for (param in parameters) {
558594
ClassNode parameterType = param.type

ast/src/test/groovy/org/particleframework/aop/infra/AopSetupSpec.groovy

+19-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
*/
1616
package org.particleframework.aop.infra
1717

18+
import org.particleframework.aop.Intercepted
1819
import org.particleframework.aop.internal.AopAttributes
1920
import org.particleframework.context.BeanContext
2021
import org.particleframework.context.DefaultBeanContext
22+
import org.particleframework.inject.BeanDefinition
23+
import spock.lang.Ignore
2124
import spock.lang.Specification
2225

2326
/**
@@ -26,17 +29,31 @@ import spock.lang.Specification
2629
*/
2730
class AopSetupSpec extends Specification {
2831

32+
@Ignore
2933
void "test AOP setup"() {
3034
given:
3135
BeanContext beanContext = new DefaultBeanContext().start()
3236

37+
when:"the bean definition is obtained"
38+
BeanDefinition<Foo> beanDefinition = beanContext.findBeanDefinition(Foo).get()
39+
40+
then:
41+
beanDefinition.findMethod("blah", String).isPresent()
42+
// should not be a reflection based method
43+
beanDefinition.findMethod("blah", String).get().getClass().getName().contains("Reflection")
44+
3345
when:
3446
Foo foo = beanContext.getBean(Foo)
3547

48+
3649
then:
37-
foo instanceof Foo$Intercepted
50+
foo instanceof Intercepted
51+
beanContext.findExecutableMethod(Foo, "blah", String).isPresent()
52+
// should not be a reflection based method
53+
!beanContext.findExecutableMethod(Foo, "blah", String).get().getClass().getName().contains("Reflection")
3854
foo.blah("test") == "Name is test"
3955
AopAttributes.@attributes.get() == null
56+
4057
}
4158

4259
void "test AOP setup attributes"() {
@@ -47,7 +64,7 @@ class AopSetupSpec extends Specification {
4764
Foo foo = beanContext.getBean(Foo)
4865
def attrs = AopAttributes.get(Foo, "blah", String)
4966
then:
50-
foo instanceof Foo$Intercepted
67+
foo instanceof Intercepted
5168
foo.blah("test") == "Name is test"
5269
AopAttributes.@attributes.get().values().first().values == attrs
5370

0 commit comments

Comments
 (0)