/*
* 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 java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.java.ast.JavaAstScanner;
import org.sonar.java.ast.visitors.SubscriptionVisitor;
import org.sonar.java.model.JavaTree;
import org.sonar.java.model.VisitorsBridge;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MethodMatcherTest {
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void should_fail_if_addParameter_is_called_after_withAnyParameters() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name")
.withAnyParameters();
exception.expect(IllegalStateException.class);
matcher.addParameter("int");
}
@Test
public void should_fail_if_addParameter_is_called_after_withoutParameter() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name")
.withoutParameter();
exception.expect(IllegalStateException.class);
matcher.addParameter("int");
}
@Test
public void should_fail_if_withAnyParameters_is_called_after_addParameter() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name").addParameter("int");
exception.expect(IllegalStateException.class);
matcher.withAnyParameters();
}
@Test
public void should_fail_if_withAnyParameters_is_called_after_withoutParameter() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name")
.withoutParameter();
exception.expect(IllegalStateException.class);
matcher.withAnyParameters();
}
@Test
public void should_fail_if_withAnyParameters_is_called_after_empty_parameters() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name").parameters(new String[0]);
exception.expect(IllegalStateException.class);
matcher.withAnyParameters();
}
@Test
public void should_fail_if_withAnyParameters_is_called_after_parameters() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name").parameters("int", "int");
exception.expect(IllegalStateException.class);
matcher.withAnyParameters();
}
@Test
public void should_fail_if_withoutParameter_is_called_after_parameters() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name").parameters("int", "int");
exception.expect(IllegalStateException.class);
matcher.withoutParameter();
}
@Test
public void should_fail_if_withAnyParameters_is_called_after_empty_parameters_TypeCriteria() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name").parameters(new TypeCriteria[0]);
exception.expect(IllegalStateException.class);
matcher.withAnyParameters();
}
@Test
public void should_fail_if_withAnyParameters_is_called_after_parameters_TypeCriteria() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name").parameters(TypeCriteria.is("int"));
exception.expect(IllegalStateException.class);
matcher.withAnyParameters();
}
@Test
public void should_fail_if_name_called_twice() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("name");
exception.expect(IllegalStateException.class);
matcher.name("otherName");
}
@Test
public void should_fail_if_name_criteria_called_twice() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name(NameCriteria.is("name"));
exception.expect(IllegalStateException.class);
matcher.name(NameCriteria.any());
}
@Test
public void should_fail_if_typeDefinition_called_twice() throws Exception {
MethodMatcher matcher = MethodMatcher.create().typeDefinition("int");
exception.expect(IllegalStateException.class);
matcher.typeDefinition("long");
}
@Test
public void should_fail_if_typeDefinition_criteria_called_twice() throws Exception {
MethodMatcher matcher = MethodMatcher.create().typeDefinition(TypeCriteria.is("int"));
exception.expect(IllegalStateException.class);
matcher.typeDefinition(TypeCriteria.anyType());
}
@Test
public void should_fail_if_parameters_are_not_defined() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("toString");
MethodTree tree = methodTreeMock("toString", mock(Symbol.TypeSymbol.class));
exception.expect(IllegalStateException.class);
matcher.matches(tree);
}
private MethodTree methodTreeMock(String methodName, @Nullable Symbol.TypeSymbol enclosingClass) {
Symbol.MethodSymbol methodSymbol = mock(Symbol.MethodSymbol.class);
when(methodSymbol.isMethodSymbol()).thenReturn(true);
when(methodSymbol.name()).thenReturn(methodName);
when(methodSymbol.enclosingClass()).thenReturn(enclosingClass);
MethodTree tree = mock(MethodTree.class);
when(tree.symbol()).thenReturn(methodSymbol);
return tree;
}
@Test
public void should_fail_if_name_is_not_defined() throws Exception {
MethodMatcher matcher = MethodMatcher.create().withoutParameter();
MethodTree tree = methodTreeMock("toString", mock(Symbol.TypeSymbol.class));
exception.expect(IllegalStateException.class);
matcher.matches(tree);
}
@Test
public void does_not_match_without_enclosingClass() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name("toString").withoutParameter();
Symbol.MethodSymbol symbol = mock(Symbol.MethodSymbol.class);
when(symbol.enclosingClass()).thenReturn(null);
MethodTree tree = mock(MethodTree.class);
when(tree.symbol()).thenReturn(symbol);
assertThat(matcher.matches(tree)).isFalse();
}
@Test
public void does_not_match_without_callSite_enclosingClass() throws Exception {
MethodMatcher matcher = MethodMatcher.create().name(NameCriteria.any()).withAnyParameters().callSite(TypeCriteria.anyType());
Symbol symbol = mock(Symbol.class);
when(symbol.enclosingClass()).thenReturn(null);
IdentifierTree id = mock(IdentifierTree.class);
when(id.is(Tree.Kind.IDENTIFIER)).thenReturn(true);
when(id.symbol()).thenReturn(symbol);
MethodInvocationTree tree = mock(MethodInvocationTree.class);
when(tree.methodSelect()).thenReturn(id);
assertThat(matcher.matches(tree)).isFalse();
}
@Test
public void detected() {
MethodMatcher objectToString = MethodMatcher.create().typeDefinition(TypeCriteria.subtypeOf("java.lang.Object")).name("toString").withoutParameter();
MethodMatcher objectToStringWithIntParam = MethodMatcher.create().name("toString").parameters("int");
MethodMatcher objectToStringWithStringParam = MethodMatcher.create().typeDefinition(TypeCriteria.anyType()).name(NameCriteria.is("toString")).parameters("java.lang.String");
MethodMatcher objectToStringWithAnyParam = MethodMatcher.create().typeDefinition(TypeCriteria.is("Test")).name("toString").withAnyParameters();
MethodMatcher integerToString = MethodMatcher.create().typeDefinition("java.lang.Integer").name("toString").withoutParameter();
MethodMatcher callSiteIsTest = MethodMatcher.create().typeDefinition(TypeCriteria.anyType()).name(NameCriteria.any()).withAnyParameters().callSite(TypeCriteria.is("Test"));
Map<MethodMatcher, List<Integer>> matches = new HashMap<>();
matches.put(objectToString, new ArrayList<>());
matches.put(objectToStringWithIntParam, new ArrayList<>());
matches.put(objectToStringWithStringParam, new ArrayList<>());
matches.put(objectToStringWithAnyParam, new ArrayList<>());
matches.put(integerToString, new ArrayList<>());
matches.put(callSiteIsTest, new ArrayList<>());
JavaAstScanner.scanSingleFileForTests(new File("src/test/files/matcher/Test.java"), new VisitorsBridge(new Visitor(matches)));
assertThat(matches.get(objectToString)).containsExactly(6, 19, 27);
assertThat(matches.get(objectToStringWithIntParam)).containsExactly(10);
assertThat(matches.get(objectToStringWithStringParam)).containsExactly(11, 14);
assertThat(matches.get(objectToStringWithAnyParam)).containsExactly(6, 10, 11, 14);
assertThat(matches.get(integerToString)).containsExactly(19);
assertThat(matches.get(callSiteIsTest)).containsExactly(6, 10, 11, 14, 18, 22);
}
class Visitor extends SubscriptionVisitor {
public Map<MethodMatcher, List<Integer>> matches;
public Visitor(Map<MethodMatcher, List<Integer>> matches) {
this.matches = matches;
}
@Override
public List<Tree.Kind> nodesToVisit() {
return ImmutableList.of(Tree.Kind.METHOD, Tree.Kind.METHOD_INVOCATION, Tree.Kind.NEW_CLASS);
}
@Override
public void visitNode(Tree tree) {
super.visitNode(tree);
for (Map.Entry<MethodMatcher, List<Integer>> entry : matches.entrySet()) {
boolean match = false;
MethodMatcher matcher = entry.getKey();
if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
match = matcher.matches((MethodInvocationTree) tree);
} else if (tree.is(Tree.Kind.METHOD)) {
match = matcher.matches((MethodTree) tree);
} else if (tree.is(Tree.Kind.NEW_CLASS)) {
match = matcher.matches((NewClassTree) tree);
}
if (match) {
entry.getValue().add(((JavaTree) tree).getLine());
}
}
}
}
}