blob: 4e28224e5d60fc6d02b1772e56d26e2ab71ccac8 [file] [log] [blame]
/*
* Copyright 2015 Google LLC
*
* 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.google.auto.value.processor;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.truth.Expect;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Validates the assumptions AutoValue makes about Guava immutable collection builders. We expect
* for each public class {@code com.google.common.collect.ImmutableFoo} that:
*
* <ul>
* <li>it contains a public nested class {@code ImmutableFoo.Builder} with the same type
* parameters;
* <li>there is a public static method {@code ImmutableFoo.builder()} that returns {@code
* ImmutableFoo.Builder};
* <li>there is a method {@code ImmutableFoo.Builder.build()} that returns {@code ImmutableFoo};
* <li>and there is a method in {@code ImmutableFoo.Builder} called either {@code addAll} or
* {@code putAll} with a single parameter to which {@code ImmutableFoo} can be assigned.
* </ul>
*
* @author emcmanus@google.com (Éamonn McManus)
*/
@RunWith(JUnit4.class)
public class GuavaCollectionBuildersTest {
private static final ImmutableSet<String> NON_BUILDABLE_COLLECTIONS =
ImmutableSet.of("ImmutableCollection");
@Rule public final Expect expect = Expect.create();
@Test
public void testImmutableBuilders() throws Exception {
ClassPath classPath = ClassPath.from(getClass().getClassLoader());
ImmutableSet<ClassPath.ClassInfo> classes = classPath.getAllClasses();
int checked = 0;
for (ClassPath.ClassInfo classInfo : classes) {
if (classInfo.getPackageName().equals("com.google.common.collect")
&& classInfo.getSimpleName().startsWith("Immutable")
&& !NON_BUILDABLE_COLLECTIONS.contains(classInfo.getSimpleName())) {
Class<?> c = Class.forName(classInfo.getName());
if (Modifier.isPublic(c.getModifiers())) {
checked++;
checkImmutableClass(c);
}
}
}
expect.that(checked).isGreaterThan(10);
}
private void checkImmutableClass(Class<?> c)
throws ClassNotFoundException, NoSuchMethodException {
if (!Modifier.isPublic(c.getModifiers())) {
return;
}
// We have a public static ImmutableFoo.builder()
Method builderMethod = c.getMethod("builder");
assertThat(Modifier.isStatic(builderMethod.getModifiers())).isTrue();
// Its return type is Builder with the same type parameters.
Type builderMethodReturn = builderMethod.getGenericReturnType();
expect.that(builderMethodReturn).isInstanceOf(ParameterizedType.class);
ParameterizedType builderMethodParameterizedReturn = (ParameterizedType) builderMethodReturn;
Class<?> builderClass = Class.forName(c.getName() + "$Builder");
expect.that(builderMethod.getReturnType()).isEqualTo(builderClass);
expect
.withMessage(c.getName())
.that(Arrays.toString(builderMethodParameterizedReturn.getActualTypeArguments()))
.isEqualTo(Arrays.toString(builderClass.getTypeParameters()));
// The Builder has a public build() method that returns ImmutableFoo.
Method buildMethod = builderClass.getMethod("build");
expect.that(buildMethod.getReturnType()).isEqualTo(c);
// The Builder has either an addAll or a putAll public method with a parameter that
// ImmutableFoo can be assigned to.
boolean found = false;
for (Method m : builderClass.getMethods()) {
if ((m.getName().equals("addAll") || m.getName().equals("putAll"))
&& m.getParameterTypes().length == 1) {
Class<?> parameter = m.getParameterTypes()[0];
if (parameter.isAssignableFrom(c)) {
found = true;
break;
}
}
}
expect.withMessage(builderClass.getName() + " has addAll or putAll").that(found).isTrue();
}
}