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

org.openrewrite.java.MethodMatcher Maven / Gradle / Ivy

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

* https://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.openrewrite.java; import lombok.Getter; import lombok.Setter; import lombok.experimental.NonFinal; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.TerminalNode; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.internal.grammar.AspectJLexer; import org.openrewrite.java.internal.grammar.RefactorMethodSignatureParser; import org.openrewrite.java.internal.grammar.RefactorMethodSignatureParserBaseVisitor; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import static java.util.stream.Collectors.joining; @Getter public class MethodMatcher { private Pattern targetTypePattern; private Pattern methodNamePattern; private Pattern argumentPattern; public MethodMatcher(String signature) { RefactorMethodSignatureParser parser = new RefactorMethodSignatureParser(new CommonTokenStream(new AspectJLexer( CharStreams.fromString(signature)))); new RefactorMethodSignatureParserBaseVisitor() { @Override public Void visitMethodPattern(RefactorMethodSignatureParser.MethodPatternContext ctx) { targetTypePattern = Pattern.compile(new TypeVisitor().visitTargetTypePattern(ctx.targetTypePattern())); methodNamePattern = Pattern.compile(ctx.simpleNamePattern().children.stream() .map(c -> AspectjUtils.aspectjNameToPattern(c.toString())) .collect(joining(""))); argumentPattern = Pattern.compile(new FormalParameterVisitor().visitFormalParametersPattern( ctx.formalParametersPattern())); return null; } }.visit(parser.methodPattern()); } public boolean matches(J.MethodDecl method, J.ClassDecl enclosing) { if (enclosing.getType() == null) { return false; } String signaturePattern = method.getParams().getParams().stream() .map(v -> v instanceof J.VariableDecls ? ((J.VariableDecls) v).getTypeAsClass() : null) .filter(Objects::nonNull) .map(this::typePattern) .filter(Objects::nonNull) .collect(joining(",")); return matchesTargetType(TypeUtils.asClass(enclosing.getType())) && methodNamePattern.matcher(method.getSimpleName()).matches() && argumentPattern.matcher(signaturePattern).matches(); } public boolean matches(J.MethodInvocation method) { if (method.getType() == null || method.getType().getDeclaringType() == null) { return false; } if (method.getType().getResolvedSignature() == null) { // no way to verify the parameter list return false; } String resolvedSignaturePattern = method.getType().getResolvedSignature().getParamTypes().stream() .map(this::typePattern) .filter(Objects::nonNull) .collect(joining(",")); return matchesTargetType(method.getType().getDeclaringType()) && methodNamePattern.matcher(method.getSimpleName()).matches() && argumentPattern.matcher(resolvedSignaturePattern).matches(); } public boolean matches(J.MethodDecl method) { throw new RuntimeException("Not implemented"); } public boolean matches(J.NewClass constructor) { if (constructor.getType() == null) { return false; } String signaturePattern = Optional.ofNullable(constructor.getArgs()) .map(args -> args.getArgs().stream() .map(Expression::getType) .map(this::typePattern) .filter(Objects::nonNull) .collect(joining(",")) ).orElse(""); JavaType.Class type = TypeUtils.asClass(constructor.getType()); assert type != null; return matchesTargetType(type) && methodNamePattern.matcher(type.getClassName()).matches() && argumentPattern.matcher(signaturePattern).matches(); } boolean matchesTargetType(@Nullable JavaType.FullyQualified type) { JavaType.Class asClass = TypeUtils.asClass(type); return type != null && (targetTypePattern.matcher(type.getFullyQualifiedName()).matches() || type != JavaType.Class.OBJECT && (asClass == null || matchesTargetType(asClass.getSupertype() == null ? JavaType.Class.OBJECT : asClass.getSupertype()))); } @Nullable private String typePattern(JavaType type) { if (type instanceof JavaType.Primitive) { return ((JavaType.Primitive) type).getKeyword(); } else if (type instanceof JavaType.Class) { return ((JavaType.Class) type).getFullyQualifiedName(); } else if (type instanceof JavaType.Array) { return typePattern(((JavaType.Array) type).getElemType()) + "[]"; } return null; } } class TypeVisitor extends RefactorMethodSignatureParserBaseVisitor { @Override public String visitClassNameOrInterface(RefactorMethodSignatureParser.ClassNameOrInterfaceContext ctx) { String className = ctx.children.stream() .map(c -> AspectjUtils.aspectjNameToPattern(c.getText())) .collect(joining("")); if (!className.contains(".")) { try { int arrInit = className.lastIndexOf("\\["); Class.forName("java.lang." + (arrInit == -1 ? className : className.substring(0, arrInit)), false, TypeVisitor.class.getClassLoader()); return "java.lang." + className; } catch (ClassNotFoundException ignored) { } } return className; } @Override public String visitPrimitiveType(RefactorMethodSignatureParser.PrimitiveTypeContext ctx) { return ctx.getText(); } } /** * 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 extends RefactorMethodSignatureParserBaseVisitor { private final List arguments = new ArrayList<>(); @Override public String visitTerminal(TerminalNode node) { if ("...".equals(node.getText())) { ((Argument.FormalType) arguments.get(arguments.size() - 1)).setVariableArgs(true); } return super.visitTerminal(node); } @Override public String visitDotDot(RefactorMethodSignatureParser.DotDotContext ctx) { arguments.add(Argument.DOT_DOT); return super.visitDotDot(ctx); } @Override public String visitFormalTypePattern(RefactorMethodSignatureParser.FormalTypePatternContext ctx) { arguments.add(new Argument.FormalType(ctx)); return super.visitFormalTypePattern(ctx); } @Override public String visitFormalParametersPattern(RefactorMethodSignatureParser.FormalParametersPatternContext ctx) { super.visitFormalParametersPattern(ctx); List argumentPatterns = new ArrayList<>(); for (int i = 0; i < arguments.size(); i++) { Argument argument = arguments.get(i); // Note: the AspectJ grammar doesn't allow for multiple ..'s in one formal parameter pattern if (argument == Argument.DOT_DOT) { if (arguments.size() == 1) { argumentPatterns.add("(" + argument.getRegex() + ")?"); } else if (i > 0) { argumentPatterns.add("(," + argument.getRegex() + ")?"); } else { argumentPatterns.add("(" + argument.getRegex() + ",)?"); } } else { // FormalType if (i > 0 && arguments.get(i - 1) != Argument.DOT_DOT) { argumentPatterns.add("," + argument.getRegex()); } else { argumentPatterns.add(argument.getRegex()); } } } return String.join("", argumentPatterns).replace("...", "\\[\\]"); } private static abstract class Argument { abstract String getRegex(); private static final Argument DOT_DOT = new Argument() { @Override String getRegex() { return "([^,]+,)*([^,]+)"; } }; static class FormalType extends Argument { RefactorMethodSignatureParser.FormalTypePatternContext ctx; @Setter @NonFinal boolean variableArgs = false; public FormalType(RefactorMethodSignatureParser.FormalTypePatternContext ctx) { this.ctx = ctx; } @Override String getRegex() { String baseType = new TypeVisitor().visitFormalTypePattern(ctx); return baseType + (variableArgs ? "\\[\\]" : ""); } } } } class AspectjUtils { private AspectjUtils() { } /** * 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.". */ public static String aspectjNameToPattern(String name) { return name .replace("[", "\\[") .replace("]", "\\]") .replaceAll("([^.])*\\.([^.])*", "$1\\.$2") .replace("*", "[^.]*") .replace("..", "\\.(.+\\.)?"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy