/*
* Copyright 2014 Google Inc. All rights reserved.
*
* 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 org.inferred.freebuilder.processor;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import org.inferred.freebuilder.processor.Analyser.CannotGenerateCodeException;
import org.inferred.freebuilder.processor.util.testing.ModelRule;
import org.junit.ClassRule;
import org.junit.Test;
import java.io.IOException;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleTypeVisitor6;
public class MethodFinderTest {
@ClassRule public static ModelRule model = new ModelRule();
abstract static class ClassOne {
abstract void method();
abstract void method(int x);
abstract int method(double x);
}
@Test
public void testNoInheritanceClass() {
assertThat(methodsOn(ClassOne.class)).containsExactly(
"void ClassOne::method()", "void ClassOne::method(int)", "int ClassOne::method(double)");
}
private interface InterfaceOne {
void method();
}
@Test
public void testNoInheritanceInterface() {
assertThat(methodsOn(InterfaceOne.class)).containsExactly("void InterfaceOne::method()");
}
private abstract static class SingleInterface implements InterfaceOne { }
@Test
public void testSingleInterface() {
assertThat(methodsOn(SingleInterface.class)).containsExactly("void InterfaceOne::method()");
}
private abstract static class SingleSuperclass extends ClassOne { }
@Test
public void testSingleSuperclassMethodInterface() {
assertThat(methodsOn(SingleSuperclass.class)).containsExactly(
"void ClassOne::method()", "void ClassOne::method(int)", "int ClassOne::method(double)");
}
interface InterfaceTwo extends InterfaceOne { }
@Test
public void testSimpleInterfaceHierarchy() {
assertThat(methodsOn(InterfaceTwo.class)).containsExactly("void InterfaceOne::method()");
}
private abstract static class DiamondInheritance
extends SingleInterface implements InterfaceTwo { }
@Test
public void testDiamondInheritance() {
assertThat(methodsOn(DiamondInheritance.class)).containsExactly("void InterfaceOne::method()");
}
private interface InterfaceThree {
void method();
}
private interface InterfaceFour {
void method();
}
private abstract static class MultipleMethodsSameSignature
implements InterfaceOne, InterfaceTwo, InterfaceThree, InterfaceFour { }
@Test
public void testMultipleMethodsSameSignature() {
ImmutableList<String> methods = methodsOn(MultipleMethodsSameSignature.class);
// When choosing between multiple unrelated interfaces defining the same method, pick any
assertThat(methods).containsAnyOf(
"void InterfaceOne::method()",
"void InterfaceThree::method()",
"void InterfaceFour::method()");
assertThat(methods).hasSize(1);
}
private abstract static class MultipleMethodsSameSignatureWithSuperclass
extends ClassOne implements InterfaceOne { }
@Test
public void testMultipleMethodsSameSignatureWithSuperclass() {
// When choosing between InterfaceOne::method and ClassOne::method, pick the concrete type.
assertThat(methodsOn(MultipleMethodsSameSignatureWithSuperclass.class)).containsExactly(
"void ClassOne::method()", "void ClassOne::method(int)", "int ClassOne::method(double)");
}
private interface MultipleMethodsSameSignatureRedeclared
extends InterfaceOne, InterfaceTwo, InterfaceThree, InterfaceFour {
@Override
void method();
}
@Test
public void testMultipleMethodsSameSignatureRedeclared() {
ImmutableList<String> methods = methodsOn(MultipleMethodsSameSignatureRedeclared.class);
// When choosing between multiple interfaces defining the same method, pick the most derived
// one.
assertThat(methods).containsExactly("void MultipleMethodsSameSignatureRedeclared::method()");
}
private abstract static class WideMethodsSuperclass {
abstract Object doSomething(Integer x) throws IOException;
}
private static class NarrowMethodSubclass extends WideMethodsSuperclass {
@Override Integer doSomething(Integer x) {
throw new UnsupportedOperationException();
}
}
@Test
public void testSignatureNarrowing() {
assertThat(methodsOn(NarrowMethodSubclass.class))
.containsExactly("Integer NarrowMethodSubclass::doSomething(Integer)");
}
private interface Receiver<T> {
void accept(T object);
}
private static class MySink implements Receiver<String> {
@Override public void accept(String object) {
throw new UnsupportedOperationException();
}
}
@Test
public void testGenericSignatureOverriding() {
assertThat(methodsOn(MySink.class)).containsExactly("void MySink::accept(String)");
}
// Utility methods
///////////////////////////////////////////////////////////////////////////////////////////////
private static ImmutableList<String> methodsOn(Class<?> cls) {
try {
return toStrings(MethodFinder.methodsOn(model.typeElement(cls), model.elementUtils()));
} catch (CannotGenerateCodeException e) {
throw new AssertionError(e);
}
}
private static ImmutableList<String> toStrings(Iterable<? extends ExecutableElement> methods) {
ImmutableList.Builder<String> resultBuilder = ImmutableList.builder();
for (ExecutableElement method : methods) {
resultBuilder.add(
STRINGIFY.visit(method.getReturnType()) + " "
+ method.getEnclosingElement().getSimpleName() + "::" + method.getSimpleName() + "("
+ Joiner.on(", ").join(variablesToStrings(method.getParameters())) + ")");
}
return resultBuilder.build();
}
private static ImmutableList<CharSequence> variablesToStrings(
Iterable<? extends VariableElement> variables) {
ImmutableList.Builder<CharSequence> resultBuilder = ImmutableList.builder();
for (VariableElement variable : variables) {
resultBuilder.add(STRINGIFY.visit(variable.asType()));
}
return resultBuilder.build();
}
private static final SimpleTypeVisitor6<CharSequence, ?> STRINGIFY =
new SimpleTypeVisitor6<CharSequence, Void>() {
@Override public Name visitDeclared(DeclaredType t, Void p) {
return t.asElement().getSimpleName();
}
@Override protected String defaultAction(TypeMirror e, Void p) {
return e.toString();
}
};
}