All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.rewrite.search.MethodMatcher.kt Maven / Gradle / Ivy

/**
 * Copyright 2016 Netflix, Inc.
 *
 * 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 com.netflix.rewrite.search

import com.netflix.rewrite.ast.Tr
import com.netflix.rewrite.ast.Type
import com.netflix.rewrite.grammar.AspectJLexer
import com.netflix.rewrite.grammar.RefactorMethodSignatureParser
import com.netflix.rewrite.grammar.RefactorMethodSignatureParserBaseVisitor
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.tree.TerminalNode
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(CharStreams.fromString(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(meth: Tr.MethodInvocation): Boolean {
        val targetType = meth.type?.declaringType?.fullyQualifiedName ?: return false
        if(meth.type.resolvedSignature == null) {
            // no way to verify the parameter list
            return false
        }
        val resolvedSignaturePattern = meth.type.resolvedSignature.paramTypes.map { type ->
            fun typePattern(type: Type): String? = when (type) {
                is Type.Primitive -> type.keyword
                is Type.Class -> type.fullyQualifiedName
                is Type.Array -> typePattern(type.elemType) + "[]"
                else -> null
            }
            typePattern(type) ?: false
        }.joinToString(",")

        return targetTypePattern.matches(targetType) &&
                methodNamePattern.matches(meth.simpleName) &&
                argumentPattern.matches(resolvedSignaturePattern)
    }
}

/**
 * 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