/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.matcher;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
import org.junit.Test;
import org.sonar.java.ast.JavaAstScanner;
import org.sonar.java.ast.visitors.SubscriptionVisitor;
import org.sonar.java.model.VisitorsBridge;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
public class MethodMatcherFactoryTest {
@Test
public void fail_arg() throws Exception {
try {
MethodMatcherFactory.methodMatcher("org.sonar.test.Test$match");
fail("Argument should not be accepted.");
} catch (IllegalArgumentException iae) {
assertThat(iae.getMessage()).contains("method");
}
try {
MethodMatcherFactory.constructorMatcher(" %");
fail("Argument should not be accepted.");
} catch (IllegalArgumentException iae) {
assertThat(iae.getMessage()).contains("constructor");
}
try {
MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match(java.lang.String;int)");
fail("Argument should not be accepted.");
} catch (IllegalArgumentException iae) {
assertThat(iae.getMessage()).contains("constructor").contains("method");
}
try {
MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match(java.lang.String,int)followed by anything");
fail("Argument should not be accepted.");
} catch (IllegalArgumentException iae) {
assertThat(iae.getMessage()).contains("constructor").contains("method");
}
try {
MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match this is an error");
fail("Argument should not be accepted.");
} catch (IllegalArgumentException iae) {
assertThat(iae.getMessage()).contains("method");
}
}
@Test
public void inner_classes() throws Exception {
MethodMatcher anyArg = MethodMatcherFactory.methodMatcher("org.sonar.test.Outer$Inner#foo");
MethodVisitor visitor = new MethodVisitor();
visitor.add(anyArg);
JavaAstScanner.scanSingleFileForTests(new File("src/test/files/matcher/InnerClass.java"), new VisitorsBridge(visitor));
assertThat(visitor.count(anyArg)).isEqualTo(1);
}
@Test
public void methodFactoryMatching() {
MethodMatcher anyArg = MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match");
MethodMatcher stringOnly = MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match(java.lang.String)");
MethodMatcher stringInt = MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match(java.lang.String,int)");
MethodMatcher intInt = MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match(int,int)");
MethodMatcher onlyBoolean = MethodMatcherFactory.methodMatcher("org.sonar.test.Test#match(java.lang.Boolean)");
MethodVisitor visitor = new MethodVisitor();
visitor.add(anyArg);
visitor.add(stringOnly);
visitor.add(stringInt);
visitor.add(intInt);
visitor.add(onlyBoolean);
File testFile = buildTestFile(
"package org.sonar.test;",
"private class Test {",
" private void match(String a) {}",
" private void match(String a, int b) {}",
" private void match(int a, int b) {}",
" private void caller() {",
" match(new String());",
" match(new String(), 0);",
" match(new String(), 1);",
" match(0, 1);",
" match(1, 2);",
" match(3, 5);",
" }",
"}");
JavaAstScanner.scanSingleFileForTests(testFile, new VisitorsBridge(visitor));
assertThat(visitor.count(anyArg)).isEqualTo(6);
assertThat(visitor.count(stringOnly)).isEqualTo(1);
assertThat(visitor.count(stringInt)).isEqualTo(2);
assertThat(visitor.count(intInt)).isEqualTo(3);
assertThat(visitor.count(onlyBoolean)).isEqualTo(0);
}
@Test
public void constructorFactoryMatching() {
MethodMatcher anyArg = MethodMatcherFactory.constructorMatcher("java.lang.String");
MethodMatcher noArg = MethodMatcherFactory.constructorMatcher("java.lang.String()");
MethodMatcher stringBuilder = MethodMatcherFactory.constructorMatcher("java.lang.String(java.lang.StringBuilder)");
MethodMatcher stringBytes = MethodMatcherFactory.constructorMatcher("java.lang.String(byte[],int,int)");
MethodVisitor visitor = new MethodVisitor();
visitor.add(anyArg);
visitor.add(noArg);
visitor.add(stringBuilder);
visitor.add(stringBytes);
File testFile = buildTestFile(
"package org.sonar.test;",
"private class Test {",
" private void caller() {",
" byte[] bytes = \"Hello world!\".getBytes();",
" new String();",
" new String(new StringBuilder());",
" new String(bytes, 6, 5);",
" new String(bytes, 0, 5);",
" }",
"}");
JavaAstScanner.scanSingleFileForTests(testFile, new VisitorsBridge(visitor));
assertThat(visitor.count(anyArg)).isEqualTo(4);
assertThat(visitor.count(noArg)).isEqualTo(1);
assertThat(visitor.count(stringBuilder)).isEqualTo(1);
assertThat(visitor.count(stringBytes)).isEqualTo(2);
}
public static File buildTestFile(String... codeLines) {
try {
File file = File.createTempFile("InLineTest", ".java");
file.deleteOnExit();
try (PrintWriter printer = new PrintWriter(file)) {
for (String line : codeLines) {
printer.println(line);
}
}
return file;
} catch (IOException e) {
Assert.fail("Unable to create inline test file: " + e.getMessage());
return null;
}
}
private static class MethodVisitor extends SubscriptionVisitor {
private final Map<MethodMatcher, Integer> matches = new HashMap<>();
void add(MethodMatcher matcher) {
matches.put(matcher, Integer.valueOf(0));
}
int count(MethodMatcher matcher) {
// Generates an NPE if the matcher was not registered, but this OK for a test.
return matches.get(matcher).intValue();
}
@Override
public void visitNode(Tree tree) {
super.visitNode(tree);
if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
visitMethodInvocation((MethodInvocationTree) tree);
} else if (tree.is(Tree.Kind.NEW_CLASS)) {
visitNewClass((NewClassTree) tree);
}
}
public void visitNewClass(NewClassTree tree) {
for (MethodMatcher matcher : matches.keySet()) {
if (matcher.matches(tree)) {
countMatch(matcher);
}
}
}
private void visitMethodInvocation(MethodInvocationTree tree) {
for (MethodMatcher matcher : matches.keySet()) {
if (matcher.matches(tree)) {
countMatch(matcher);
}
}
}
public void countMatch(MethodMatcher matcher) {
int n = matches.get(matcher).intValue() + 1;
matches.put(matcher, Integer.valueOf(n));
}
@Override
public List<Kind> nodesToVisit() {
return ImmutableList.of(Tree.Kind.METHOD_INVOCATION, Tree.Kind.NEW_CLASS);
}
}
}