org.glowroot.weaving.AnalyzingClassVisitor Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glowroot.weaving;
import java.lang.reflect.Modifier;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.collect.Lists;
import org.glowroot.shaded.google.common.collect.Sets;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.shaded.objectweb.asm.ClassVisitor;
import org.glowroot.shaded.objectweb.asm.MethodVisitor;
import org.glowroot.shaded.objectweb.asm.Type;
import org.glowroot.common.ClassNames;
import org.glowroot.weaving.WeavingClassVisitor.ShortCircuitException;
import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;
import static org.glowroot.shaded.objectweb.asm.Opcodes.ASM5;
class AnalyzingClassVisitor extends ClassVisitor {
private final ImmutableList shimTypes;
private final ImmutableList mixinTypes;
private final ImmutableList advisors;
private final @Nullable ClassLoader loader;
private final AnalyzedWorld analyzedWorld;
private final @Nullable CodeSource codeSource;
private ImmutableList adviceMatchers = ImmutableList.of();
private ImmutableList matchedShimTypes = ImmutableList.of();
private ImmutableList matchedMixinTypes = ImmutableList.of();
private List superAnalyzedClasses = ImmutableList.of();
private AnalyzedClass./*@MonotonicNonNull*/Builder analyzedClassBuilder;
private @MonotonicNonNull AnalyzedClass analyzedClass;
private @MonotonicNonNull String className;
private @MonotonicNonNull Set superClassNames;
public AnalyzingClassVisitor(List advisors, List shimTypes,
List mixinTypes, @Nullable ClassLoader loader, AnalyzedWorld analyzedWorld,
@Nullable CodeSource codeSource) {
super(ASM5);
this.shimTypes = ImmutableList.copyOf(shimTypes);
this.mixinTypes = ImmutableList.copyOf(mixinTypes);
this.advisors = ImmutableList.copyOf(advisors);
this.loader = loader;
this.analyzedWorld = analyzedWorld;
this.codeSource = codeSource;
}
@Override
public void visit(int version, int access, String internalName, @Nullable String signature,
@Nullable String superInternalName,
String /*@Nullable*/[] interfaceInternalNamesNullable) {
AnalyzedClass nonInterestingAnalyzedClass =
visitAndSometimesReturnNonInterestingAnalyzedClass(access, internalName,
superInternalName, interfaceInternalNamesNullable);
if (nonInterestingAnalyzedClass != null) {
analyzedClass = nonInterestingAnalyzedClass;
throw ShortCircuitException.INSTANCE;
}
}
public @Nullable AnalyzedClass visitAndSometimesReturnNonInterestingAnalyzedClass(int access,
String internalName, @Nullable String superInternalName,
String /*@Nullable*/[] interfaceInternalNamesNullable) {
ImmutableList interfaceNames =
ClassNames.fromInternalNames(interfaceInternalNamesNullable);
className = ClassNames.fromInternalName(internalName);
String superClassName = ClassNames.fromInternalName(superInternalName);
analyzedClassBuilder = AnalyzedClass.builder()
.modifiers(access)
.name(className)
.superName(superClassName)
.addAllInterfaceNames(interfaceNames);
adviceMatchers = AdviceMatcher.getAdviceMatchers(className, advisors);
if (Modifier.isInterface(access)) {
superAnalyzedClasses = ImmutableList.of();
matchedShimTypes = getMatchedShimTypes(className, ImmutableList.of(),
ImmutableList.of());
analyzedClassBuilder.addAllShimTypes(matchedShimTypes);
matchedMixinTypes = getMatchedMixinTypes(className, ImmutableList.of(),
ImmutableList.of());
analyzedClassBuilder.addAllMixinTypes(matchedMixinTypes);
if (adviceMatchers.isEmpty()) {
return analyzedClassBuilder.build();
} else {
return null;
}
}
ParseContext parseContext = ParseContext.of(className, codeSource);
List superAnalyzedHierarchy =
analyzedWorld.getAnalyzedHierarchy(superClassName, loader, parseContext);
List interfaceAnalyzedHierarchy =
getAnalyzedHierarchy(interfaceNames, parseContext);
// it's ok if there are duplicates in the superAnalyzedClasses list (e.g. an interface that
// appears twice in a type hierarchy), it's rare, dups don't cause an issue for callers, and
// so it doesn't seem worth the (minor) performance hit to de-dup every time
superAnalyzedClasses = Lists.newArrayList();
superAnalyzedClasses.addAll(superAnalyzedHierarchy);
superAnalyzedClasses.addAll(interfaceAnalyzedHierarchy);
matchedShimTypes =
getMatchedShimTypes(className, superAnalyzedHierarchy, interfaceAnalyzedHierarchy);
analyzedClassBuilder.addAllShimTypes(matchedShimTypes);
matchedMixinTypes =
getMatchedMixinTypes(className, superAnalyzedHierarchy, interfaceAnalyzedHierarchy);
analyzedClassBuilder.addAllMixinTypes(matchedMixinTypes);
if (isNonInteresting()) {
return analyzedClassBuilder.build();
} else {
return null;
}
}
@Override
public @Nullable MethodVisitor visitMethod(int access, String name, String desc,
@Nullable String signature, String /*@Nullable*/[] exceptions) {
visitMethodAndReturnAdvisors(access, name, desc, signature, exceptions);
return null;
}
@Override
public void visitEnd() {
checkNotNull(analyzedClassBuilder, "Call to visit() is required");
analyzedClass = analyzedClassBuilder.build();
}
List visitMethodAndReturnAdvisors(int access, String name, String desc,
@Nullable String signature, String /*@Nullable*/[] exceptions) {
List parameterTypes = Arrays.asList(Type.getArgumentTypes(desc));
Type returnType = Type.getReturnType(desc);
List matchingAdvisors =
getMatchingAdvisors(name, parameterTypes, returnType, access);
if (!matchingAdvisors.isEmpty()) {
checkNotNull(analyzedClassBuilder, "Call to visit() is required");
AnalyzedMethod.Builder builder = AnalyzedMethod.builder();
builder.name(name);
for (Type parameterType : parameterTypes) {
builder.addParameterTypes(parameterType.getClassName());
}
builder.returnType(returnType.getClassName())
.modifiers(access)
.signature(signature);
if (exceptions != null) {
for (String exception : exceptions) {
builder.addExceptions(ClassNames.fromInternalName(exception));
}
}
builder.addAllAdvisors(matchingAdvisors);
analyzedClassBuilder.addAnalyzedMethods(builder.build());
}
return matchingAdvisors;
}
ImmutableList getMatchedShimTypes() {
return matchedShimTypes;
}
ImmutableList getMatchedMixinTypes() {
return matchedMixinTypes;
}
List getSuperAnalyzedClasses() {
return superAnalyzedClasses;
}
@Nullable
AnalyzedClass getAnalyzedClass() {
return analyzedClass;
}
private List getAnalyzedHierarchy(ImmutableList classNames,
ParseContext parseContext) {
List analyzedHierarchy = Lists.newArrayList();
for (String className : classNames) {
analyzedHierarchy
.addAll(analyzedWorld.getAnalyzedHierarchy(className, loader, parseContext));
}
return analyzedHierarchy;
}
private ImmutableList getMatchedShimTypes(String className,
Iterable superAnalyzedClasses,
List newInterfaceAnalyzedClasses) {
Set matchedShimTypes = Sets.newHashSet();
for (ShimType shimType : shimTypes) {
if (shimType.target().equals(className)) {
matchedShimTypes.add(shimType);
}
}
for (AnalyzedClass newInterfaceAnalyzedClass : newInterfaceAnalyzedClasses) {
matchedShimTypes.addAll(newInterfaceAnalyzedClass.shimTypes());
}
// remove shims that were already implemented in a super class
for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
if (!superAnalyzedClass.isInterface()) {
matchedShimTypes.removeAll(superAnalyzedClass.shimTypes());
}
}
return ImmutableList.copyOf(matchedShimTypes);
}
private ImmutableList getMatchedMixinTypes(String className,
Iterable superAnalyzedClasses,
List newInterfaceAnalyzedClasses) {
Set matchedMixinTypes = Sets.newHashSet();
for (MixinType mixinType : mixinTypes) {
// currently only exact matching is supported
if (mixinType.targets().contains(className)) {
matchedMixinTypes.add(mixinType);
}
}
for (AnalyzedClass newInterfaceAnalyzedClass : newInterfaceAnalyzedClasses) {
matchedMixinTypes.addAll(newInterfaceAnalyzedClass.mixinTypes());
}
// remove mixins that were already implemented in a super class
for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
if (!superAnalyzedClass.isInterface()) {
matchedMixinTypes.removeAll(superAnalyzedClass.mixinTypes());
}
}
return ImmutableList.copyOf(matchedMixinTypes);
}
private boolean isNonInteresting() {
return !hasSuperAdvice() && adviceMatchers.isEmpty() && matchedShimTypes.isEmpty()
&& matchedMixinTypes.isEmpty();
}
private boolean hasSuperAdvice() {
for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
if (!superAnalyzedClass.analyzedMethods().isEmpty()) {
return true;
}
}
return false;
}
private List getMatchingAdvisors(String methodName, List parameterTypes,
Type returnType, int modifiers) {
Set matchingAdvisors = Sets.newHashSet();
for (AdviceMatcher adviceMatcher : adviceMatchers) {
if (adviceMatcher.isMethodLevelMatch(methodName, parameterTypes, returnType,
modifiers)) {
matchingAdvisors.add(adviceMatcher.advice());
}
}
// look at super types
checkNotNull(superAnalyzedClasses, "Call to visit() is required");
for (AnalyzedClass superAnalyzedClass : superAnalyzedClasses) {
for (AnalyzedMethod analyzedMethod : superAnalyzedClass.analyzedMethods()) {
if (analyzedMethod.isOverriddenBy(methodName, parameterTypes)) {
addToMatchingAdvisors(matchingAdvisors, analyzedMethod.advisors());
}
}
}
// sort since the order affects advice and timer nesting
return sortAdvisors(matchingAdvisors);
}
void addToMatchingAdvisors(Set matchingAdvisors, List advisors) {
for (Advice advice : advisors) {
if (advice.pointcut().declaringClassName().equals("")) {
matchingAdvisors.add(advice);
} else {
if (isTargetClassNameMatch(advice)) {
matchingAdvisors.add(advice);
}
}
}
}
private boolean isTargetClassNameMatch(Advice advice) {
if (superClassNames == null) {
superClassNames = buildSuperClassNames();
}
Pattern targetClassNamePattern = advice.pointcutTargetClassNamePattern();
if (targetClassNamePattern == null) {
return superClassNames.contains(advice.pointcutTargetClassName());
} else {
for (String superClassName : superClassNames) {
if (targetClassNamePattern.matcher(superClassName).matches()) {
return true;
}
}
return false;
}
}
private Set buildSuperClassNames() {
checkNotNull(className, "Call to visit() is required");
Set superClassNames = Sets.newHashSet();
superClassNames.add(className);
for (AnalyzedClass analyzedClass : superAnalyzedClasses) {
superClassNames.add(analyzedClass.name());
}
return superClassNames;
}
private List sortAdvisors(Set matchingAdvisors) {
switch (matchingAdvisors.size()) {
case 0:
return ImmutableList.of();
case 1:
return ImmutableList.copyOf(matchingAdvisors);
default:
return Advice.ordering.immutableSortedCopy(matchingAdvisors);
}
}
}