com.netflix.java.refactor.ast.MethodMatcher.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-source-refactor Show documentation
Show all versions of java-source-refactor Show documentation
Pluggable and distributed refactoring tool for Java source code
package com.netflix.java.refactor.ast
import com.netflix.java.refactor.aspectj.AspectJLexer
import com.netflix.java.refactor.aspectj.RefactorMethodSignatureParser
import com.netflix.java.refactor.aspectj.RefactorMethodSignatureParserBaseVisitor
import com.sun.tools.javac.code.Flags
import com.sun.tools.javac.code.Symbol
import com.sun.tools.javac.tree.JCTree
import org.antlr.v4.runtime.ANTLRInputStream
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.tree.TerminalNode
import org.antlr.v4.runtime.tree.Trees
import java.util.*
class MethodMatcher(signature: String) {
lateinit var targetTypePattern: Regex
lateinit var methodNamePattern: Regex
lateinit var argumentPattern: Regex
init {
val parser = RefactorMethodSignatureParser(CommonTokenStream(AspectJLexer(ANTLRInputStream(signature))))
object: RefactorMethodSignatureParserBaseVisitor() {
override fun visitMethodPattern(ctx: RefactorMethodSignatureParser.MethodPatternContext): Void? {
targetTypePattern = TypeVisitor().visitTargetTypePattern(ctx.targetTypePattern()).toRegex()
methodNamePattern = ctx.simpleNamePattern().children // all TerminalNode instances
.map { it.toString().aspectjNameToRegexSyntax() }
.joinToString("")
.toRegex()
argumentPattern = FormalParameterVisitor().visitFormalParametersPattern(ctx.formalParametersPattern()).toRegex()
return null
}
}.visit(parser.methodPattern())
}
fun matches(invocation: JCTree.JCMethodInvocation): Boolean {
val meth = invocation.meth
val (methodSymbol, name) = when(meth) {
is JCTree.JCFieldAccess -> meth.sym to meth.name
is JCTree.JCIdent -> meth.sym to meth.name
else -> return@matches false
}
val targetType = when(meth) {
is JCTree.JCFieldAccess -> {
if (meth.selected is JCTree.JCNewClass) {
// we have to do a bit of spelunking to get the type of a new expression...
(meth.selected as JCTree.JCNewClass).clazz?.type?.originalType?.toString()
} else {
meth.sym?.owner?.toString()
}
}
is JCTree.JCIdent -> {
meth.sym?.owner?.toString()
}
// this is a method invocation on a method in the same class, which we won't be refactoring on ever
else -> return@matches false
}
val args = when(methodSymbol) {
is Symbol.MethodSymbol -> methodSymbol.params().map { it.type.toString() }.joinToString(",")
// This is a weird case... for some reason the attribution phase will sometimes assign a ClassSymbol to
// method invocation, making the parameters of the resolved method inaccessible to us. In these cases,
// we can make a best effort at determining the method's argument types by observing the types that are
// being passed to it by the code.
else ->
if(invocation.args.all { it.type != null })
invocation.args.map { it.type.toString() }.joinToString(",")
else null
}
return targetType is String && targetTypePattern.matches(targetType) &&
methodNamePattern.matches(name.toString()) &&
args != null && argumentPattern.matches(args)
}
}
/**
* See https://eclipse.org/aspectj/doc/next/progguide/semantics-pointcuts.html#type-patterns
*
* An embedded * in an identifier matches any sequence of characters, but
* does not match the package (or inner-type) separator ".".
*
* The ".." wildcard matches any sequence of characters that start and end with a ".", so it can be used to pick out all
* types in any subpackage, or all inner types. e.g. within(com.xerox..*)
picks out all join points where
* the code is in any declaration of a type whose name begins with "com.xerox.".
*/
fun String.aspectjNameToRegexSyntax() = this
.replace("[", "\\[").replace("]", "\\]")
.replace("([^\\.])*.([^\\.])*", "$1\\.$2")
.replace("*", "[^\\.]*")
.replace("..", "\\.(.+\\.)?")
class TypeVisitor : RefactorMethodSignatureParserBaseVisitor() {
override fun visitClassNameOrInterface(ctx: RefactorMethodSignatureParser.ClassNameOrInterfaceContext): String {
return ctx.children // all TerminalNode instances
.map { it.text.aspectjNameToRegexSyntax() }
.joinToString("")
.let { className ->
if(!className.contains('.')) {
try {
Class.forName("java.lang.${className.substringBefore("\\[")}", false, TypeVisitor::class.java.classLoader)
return@let "java.lang.$className"
} catch(ignore: ClassNotFoundException) {
}
}
className
}
}
override fun visitPrimitiveType(ctx: RefactorMethodSignatureParser.PrimitiveTypeContext): String {
return ctx.text
}
}
/**
* The wildcard .. indicates zero or more parameters, so:
*
* execution(void m(..))
* picks out execution join points for void methods named m, of any number of arguments, while
*
* execution(void m(.., int))
* picks out execution join points for void methods named m whose last parameter is of type int.
*/
class FormalParameterVisitor: RefactorMethodSignatureParserBaseVisitor() {
private val arguments = ArrayList()
private sealed class Argument {
abstract val regex: String
object DotDot: Argument() {
override val regex = "([^,]+,)*([^,]+)"
}
class FormalType(ctx: RefactorMethodSignatureParser.FormalTypePatternContext): Argument() {
override val regex: String by lazy {
val baseType = TypeVisitor().visitFormalTypePattern(ctx)
if(variableArgs) "$baseType..." else baseType
}
var variableArgs = false
}
}
override fun visitTerminal(node: TerminalNode): String? {
if(node.text == "...") {
(arguments.last() as Argument.FormalType).variableArgs = true
}
return super.visitTerminal(node)
}
override fun visitDotDot(ctx: RefactorMethodSignatureParser.DotDotContext): String? {
arguments.add(Argument.DotDot)
return super.visitDotDot(ctx)
}
override fun visitFormalTypePattern(ctx: RefactorMethodSignatureParser.FormalTypePatternContext): String? {
arguments.add(Argument.FormalType(ctx))
return super.visitFormalTypePattern(ctx)
}
override fun visitFormalParametersPattern(ctx: RefactorMethodSignatureParser.FormalParametersPatternContext): String {
super.visitFormalParametersPattern(ctx)
return arguments.mapIndexed { i, argument ->
// Note: the AspectJ grammar doesn't allow for multiple ..'s in one formal parameter pattern
when(argument) {
is Argument.DotDot -> {
if(arguments.size == 1)
"(${argument.regex})?"
else if(i > 0)
"(,${argument.regex})?"
else "(${argument.regex},)?"
}
is Argument.FormalType -> {
if(i > 0 && arguments[i-1] !is Argument.DotDot)
",${argument.regex}"
else argument.regex
}
}
}.joinToString("").replace("...", "\\[\\]")
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy