/*
* Copyright 2015 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 com.google.errorprone.bugpatterns.threadsafety;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.common.primitives.Primitives;
import com.google.errorprone.VisitorState;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.tools.javac.code.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashSet;
import java.util.Set;
/** A collection of types with with known mutability. */
final class WellKnownMutability {
/** Types that are known to be immutable. */
static final ImmutableMap<String, ImmutableAnnotationInfo> KNOWN_IMMUTABLE =
getBootstrapClasses();
static class Builder {
final ImmutableMap.Builder<String, ImmutableAnnotationInfo> mapBuilder = ImmutableMap.builder();
public Builder addAll(Set<Class<?>> clazzs) {
for (Class<?> clazz : clazzs) {
add(clazz);
}
return this;
}
public Builder add(Class<?> clazz, String... containerOf) {
ImmutableSet<String> containerTyParams = ImmutableSet.copyOf(containerOf);
HashSet<String> actualTyParams = new HashSet<>();
for (TypeVariable<?> x : clazz.getTypeParameters()) {
actualTyParams.add(x.getName());
}
SetView<String> difference = Sets.difference(containerTyParams, actualTyParams);
if (!difference.isEmpty()) {
throw new AssertionError(
String.format(
"For %s, please update the type parameter(s) from %s to %s",
clazz, difference, actualTyParams));
}
mapBuilder.put(
clazz.getName(),
ImmutableAnnotationInfo.create(clazz.getName(), ImmutableList.copyOf(containerOf)));
return this;
}
public Builder add(String className, String... containerOf) {
mapBuilder.put(
className, ImmutableAnnotationInfo.create(className, ImmutableList.copyOf(containerOf)));
return this;
}
public ImmutableMap<String, ImmutableAnnotationInfo> build() {
return mapBuilder.build();
}
}
// TODO(b/35724557): share this list with other code analyzing types for immutability
// TODO(cushon): generate this at build-time to get type-safety without added compile-time deps
private static ImmutableMap<String, ImmutableAnnotationInfo> getBootstrapClasses() {
return new Builder()
.addAll(Primitives.allPrimitiveTypes())
.addAll(Primitives.allWrapperTypes())
.add("com.google.protobuf.ByteString")
.add("com.google.protobuf.Descriptors$Descriptor")
.add("com.google.protobuf.Descriptors$EnumDescriptor")
.add("com.google.protobuf.Descriptors$EnumValueDescriptor")
.add("com.google.protobuf.Descriptors$FieldDescriptor")
.add("com.google.protobuf.Descriptors$FileDescriptor")
.add("com.google.protobuf.Descriptors$ServiceDescriptor")
.add("com.google.protobuf.Extension")
.add("com.google.protobuf.ExtensionRegistry$ExtensionInfo")
.add("com.google.re2j.Pattern")
.add(com.google.common.base.CharMatcher.class)
.add(com.google.common.base.Converter.class)
.add(com.google.common.base.Joiner.class)
.add(com.google.common.base.Optional.class, "T")
.add(com.google.common.base.Splitter.class)
.add(com.google.common.collect.ImmutableBiMap.class, "K", "V")
.add(com.google.common.collect.ImmutableCollection.class, "E")
.add(com.google.common.collect.ImmutableList.class, "E")
.add(com.google.common.collect.ImmutableListMultimap.class, "K", "V")
.add(com.google.common.collect.ImmutableMap.class, "K", "V")
.add(com.google.common.collect.ImmutableMultimap.class, "K", "V")
.add(com.google.common.collect.ImmutableMultiset.class, "E")
.add(com.google.common.collect.ImmutableRangeMap.class, "K", "V")
.add(com.google.common.collect.ImmutableRangeSet.class, "C")
.add(com.google.common.collect.ImmutableSet.class, "E")
.add(com.google.common.collect.ImmutableSetMultimap.class, "K", "V")
.add(com.google.common.collect.ImmutableSortedMap.class, "K", "V")
.add(com.google.common.collect.ImmutableSortedMultiset.class, "E")
.add(com.google.common.collect.ImmutableSortedSet.class, "E")
.add(com.google.common.collect.ImmutableTable.class, "R", "C", "V")
.add(com.google.common.collect.Range.class, "C")
.add(com.google.common.graph.ImmutableGraph.class, "N")
.add(com.google.common.graph.ImmutableNetwork.class, "N", "E")
.add(com.google.common.graph.ImmutableValueGraph.class, "N", "V")
.add(com.google.common.hash.HashCode.class)
.add(com.google.common.io.BaseEncoding.class)
.add(com.google.common.net.MediaType.class)
.add(com.google.common.primitives.UnsignedLong.class)
.add(java.lang.Class.class)
.add(java.lang.String.class)
.add(java.lang.annotation.Annotation.class)
.add(java.math.BigDecimal.class)
.add(java.math.BigInteger.class)
.add(java.net.InetAddress.class)
.add(java.net.URI.class)
.add(java.util.Locale.class)
.add(java.util.regex.Pattern.class)
.add("android.net.Uri")
.add("java.util.Optional", "T")
.add("java.time.Duration")
.add("java.time.Instant")
.add("java.time.LocalDate")
.add("java.time.LocalDateTime")
.add("java.time.LocalTime")
.add("java.time.MonthDay")
.add("java.time.OffsetDateTime")
.add("java.time.OffsetTime")
.add("java.time.Period")
.add("java.time.Year")
.add("java.time.YearMonth")
.add("java.time.ZoneId")
.add("java.time.ZoneOffset")
.add("java.time.ZonedDateTime")
.add("java.time.chrono.AbstractChronology")
.add("java.time.chrono.ChronoLocalDate")
.add("java.time.chrono.ChronoLocalDateTime", "D")
.add("java.time.chrono.ChronoPeriod")
.add("java.time.chrono.ChronoZonedDateTime", "D")
.add("java.time.chrono.Chronology")
.add("java.time.chrono.Era")
.add("java.time.chrono.HijrahChronology")
.add("java.time.chrono.HijrahDate")
.add("java.time.chrono.IsoChronology")
.add("java.time.chrono.JapaneseChronology")
.add("java.time.chrono.JapaneseDate")
.add("java.time.chrono.JapaneseEra")
.add("java.time.chrono.MinguoChronology")
.add("java.time.chrono.MinguoDate")
.add("java.time.chrono.ThaiBuddhistChronology")
.add("java.time.chrono.ThaiBuddhistDate")
.add("java.time.format.DateTimeFormatter")
.add("java.time.format.DecimalStyle")
.add("java.time.temporal.TemporalField")
.add("java.time.temporal.TemporalUnit")
.add("java.time.temporal.ValueRange")
.add("java.time.temporal.WeekFields")
.add("java.time.zone.ZoneOffsetTransition")
.add("java.time.zone.ZoneOffsetTransitionRule")
.add("java.time.zone.ZoneRules")
.add("java.time.zone.ZoneRulesProvider")
.add("org.joda.time.DateTime")
.add("org.joda.time.DateTimeZone")
.add("org.joda.time.Duration")
.add("org.joda.time.Instant")
.add("org.joda.time.LocalDate")
.add("org.joda.time.LocalDateTime")
.add("org.joda.time.Period")
.add("org.joda.time.format.DateTimeFormatter")
.build();
}
/** Types that are known to be mutable. */
static final ImmutableSet<String> KNOWN_UNSAFE = getKnownUnsafeClasses();
private static ImmutableSet<String> getKnownUnsafeClasses() {
ImmutableSet.Builder<String> result = ImmutableSet.<String>builder();
for (Class<?> clazz :
ImmutableSet.<Class<?>>of(
java.lang.Iterable.class,
java.lang.Object.class,
java.util.ArrayList.class,
java.util.Collection.class,
java.util.List.class,
java.util.Map.class,
java.util.Set.class,
java.util.EnumSet.class,
java.util.EnumMap.class)) {
result.add(clazz.getName());
}
return result.build();
}
// ProtocolSupport matches Message (not MessageLite) for legacy reasons
private static final Supplier<Type> MESSAGE_TYPE =
Suppliers.typeFromString("com.google.protobuf.MessageLite");
private static final Supplier<Type> MUTABLE_MESSAGE_TYPE =
Suppliers.typeFromString("com.google.protobuf.MutableMessageLite");
private static final Supplier<Type> PROTOCOL_MESSAGE_TYPE =
Suppliers.typeFromString("com.google.io.protocol.ProtocolMessage");
private static boolean isAssignableTo(Type type, Supplier<Type> supplier, VisitorState state) {
Type to = supplier.get(state);
if (to == null) {
// the type couldn't be loaded
return false;
}
return state.getTypes().isAssignable(type, to);
}
/**
* Compile-time equivalent of
* {@code com.google.io.protocol.ProtocolSupport#isProto2MessageClass}.
*/
static boolean isProto2MessageClass(VisitorState state, Type type) {
checkNotNull(type);
return isAssignableTo(type, MESSAGE_TYPE, state)
&& !isAssignableTo(type, PROTOCOL_MESSAGE_TYPE, state);
}
/**
* Compile-time equivalent of
* {@code com.google.io.protocol.ProtocolSupport#isProto2MutableMessageClass}.
*/
static boolean isProto2MutableMessageClass(VisitorState state, Type type) {
checkNotNull(type);
return isAssignableTo(type, MUTABLE_MESSAGE_TYPE, state)
&& !isAssignableTo(type, PROTOCOL_MESSAGE_TYPE, state);
}
/** Returns true if the type is an annotation. */
static boolean isAnnotation(VisitorState state, Type type) {
return isAssignableTo(type, Suppliers.ANNOTATION_TYPE, state);
}
}