/*
* Copyright 2012-2017 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.agent.weaving;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.immutables.value.Value;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.glowroot.agent.plugin.api.weaving.MethodModifier;
import org.glowroot.common.util.Styles;
@Value.Immutable
@Styles.AllParameters
abstract class AdviceMatcher {
private static final Logger logger = LoggerFactory.getLogger(AdviceMatcher.class);
static ImmutableList<AdviceMatcher> getAdviceMatchers(String className,
List<String> classAnnotations, Collection<String> superClassNames,
List<Advice> advisors) {
List<AdviceMatcher> adviceMatchers = Lists.newArrayList();
for (Advice advice : advisors) {
if (isClassMatch(className, classAnnotations, superClassNames, advice)) {
adviceMatchers.add(ImmutableAdviceMatcher.of(advice));
}
}
return ImmutableList.copyOf(adviceMatchers);
}
abstract Advice advice();
boolean isMethodLevelMatch(String methodName, List<String> methodAnnotations,
List<Type> parameterTypes, Type returnType, int modifiers) {
if (!isMethodNameMatch(methodName) || !isAnnotationMatch(methodAnnotations,
advice().pointcutMethodAnnotationPattern(), advice().pointcut().methodAnnotation())
|| !isMethodParameterTypesMatch(parameterTypes)) {
return false;
}
return isMethodReturnMatch(returnType) && isMethodModifiersMatch(modifiers);
}
private boolean isMethodNameMatch(String methodName) {
if (methodName.equals("<clinit>")) {
// static initializers are not supported
return false;
}
Pattern pointcutMethodNamePattern = advice().pointcutMethodNamePattern();
if (pointcutMethodNamePattern != null) {
// don't want patterns to match constructors
return !methodName.equals("<init>")
&& pointcutMethodNamePattern.matcher(methodName).matches();
}
String pointcutMethodName = advice().pointcut().methodName();
return pointcutMethodName.isEmpty() || pointcutMethodName.equals(methodName);
}
private boolean isMethodParameterTypesMatch(List<Type> parameterTypes) {
List<Object> pointcutMethodParameterTypes = advice().pointcutMethodParameterTypes();
for (int i = 0; i < pointcutMethodParameterTypes.size(); i++) {
Object pointcutMethodParameterType = pointcutMethodParameterTypes.get(i);
if (pointcutMethodParameterType.equals("..")) {
if (i != pointcutMethodParameterTypes.size() - 1) {
logger.warn("'..' can only be used at the end of methodParameterTypes");
return false;
} else {
// ".." matches everything after this
return true;
}
}
if (parameterTypes.size() == i) {
// have run out of argument types to match
return false;
}
if (!isMethodParameterTypeMatch(pointcutMethodParameterType, parameterTypes.get(i))) {
return false;
}
}
// need this final test since argumentTypes may still have unmatched elements
return parameterTypes.size() == pointcutMethodParameterTypes.size();
}
private boolean isMethodParameterTypeMatch(Object pointcutMethodParameterType,
Type parameterType) {
String className = parameterType.getClassName();
if (pointcutMethodParameterType instanceof String) {
return pointcutMethodParameterType.equals(className);
} else {
return ((Pattern) pointcutMethodParameterType).matcher(className).matches();
}
}
private boolean isMethodReturnMatch(Type returnType) {
String pointcutMethodReturn = advice().pointcut().methodReturnType();
return pointcutMethodReturn.isEmpty()
|| pointcutMethodReturn.equals(returnType.getClassName());
}
private boolean isMethodModifiersMatch(int modifiers) {
for (MethodModifier methodModifier : advice().pointcut().methodModifiers()) {
if (!isMethodModifierMatch(methodModifier, modifiers)) {
return false;
}
}
return true;
}
private boolean isMethodModifierMatch(MethodModifier methodModifier, int modifiers) {
switch (methodModifier) {
case PUBLIC:
return Modifier.isPublic(modifiers);
case STATIC:
return Modifier.isStatic(modifiers);
case NOT_STATIC:
return !Modifier.isStatic(modifiers);
default:
return false;
}
}
private static boolean isClassMatch(String className, List<String> classAnnotations,
Collection<String> superClassNames, Advice advice) {
if (!isAnnotationMatch(classAnnotations, advice.pointcutClassAnnotationPattern(),
advice.pointcut().classAnnotation())) {
return false;
}
if (!isClassNameMatch(className, advice)) {
return false;
}
Pattern pointcutSuperTypeRestrictionPattern = advice.pointcutSuperTypeRestrictionPattern();
if (pointcutSuperTypeRestrictionPattern != null) {
for (String superClassName : superClassNames) {
if (pointcutSuperTypeRestrictionPattern.matcher(superClassName).matches()) {
return true;
}
}
return false;
} else {
String superTypeRestriction = advice.pointcut().superTypeRestriction();
return superTypeRestriction.isEmpty() || superClassNames.contains(superTypeRestriction);
}
}
private static boolean isClassNameMatch(String className, Advice advice) {
Pattern classNamePattern = advice.pointcutClassNamePattern();
if (classNamePattern != null) {
return classNamePattern.matcher(className).matches();
}
String pointcutClassName = advice.pointcut().className();
return pointcutClassName.isEmpty() || pointcutClassName.equals(className);
}
private static boolean isAnnotationMatch(List<String> annotations, @Nullable Pattern pattern,
String strictMatch) {
for (String annotation : annotations) {
annotation = annotation.replace('/', '.').substring(1, annotation.length() - 1);
if (pattern != null && pattern.matcher(annotation).matches()) {
return true;
}
if (annotation.equals(strictMatch)) {
return true;
}
}
return strictMatch.isEmpty();
}
}