blob: 01ec25e2d47f90139160955d385b05795bac9c9a [file] [log] [blame]
/*
* Copyright 2020 The Bazel Authors. 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.devtools.build.android.desugar.langmodel;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.Collection;
import org.objectweb.asm.Type;
/**
* Represents the identifiable name of a Java class or interface with convenient conversions among
* different names.
*/
@AutoValue
public abstract class ClassName implements TypeMappable<ClassName> {
public static final String IN_PROCESS_LABEL = "__desugar__/";
private static final String IMMUTABLE_LABEL_LABEL = "__final__/";
private static final String TYPE_ADAPTER_PACKAGE_ROOT = "desugar/runtime/typeadapter/";
public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
new TypeMapper(className -> className.stripPackagePrefix(IN_PROCESS_LABEL));
public static final TypeMapper IMMUTABLE_LABEL_STRIPPER =
new TypeMapper(className -> className.stripPackagePrefix(IMMUTABLE_LABEL_LABEL));
private static final String TYPE_ADAPTER_SUFFIX = "Adapter";
private static final String TYPE_CONVERTER_SUFFIX = "Converter";
/**
* The primitive type as specified at
* https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
*/
private static final ImmutableMap<String, Type> PRIMITIVES_TYPES =
ImmutableMap.<String, Type>builder()
.put("V", Type.VOID_TYPE)
.put("Z", Type.BOOLEAN_TYPE)
.put("C", Type.CHAR_TYPE)
.put("B", Type.BYTE_TYPE)
.put("S", Type.SHORT_TYPE)
.put("I", Type.INT_TYPE)
.put("F", Type.FLOAT_TYPE)
.put("J", Type.LONG_TYPE)
.put("D", Type.DOUBLE_TYPE)
.build();
/**
* The primitive type as specified at
* https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.3
*/
private static final ImmutableMap<ClassName, ClassName> PRIMITIVES_TO_BOXED_TYPES =
ImmutableMap.<ClassName, ClassName>builder()
.put(ClassName.create(Type.VOID_TYPE), ClassName.create("java/lang/Void"))
.put(ClassName.create(Type.BOOLEAN_TYPE), ClassName.create("java/lang/Boolean"))
.put(ClassName.create(Type.CHAR_TYPE), ClassName.create("java/lang/Character"))
.put(ClassName.create(Type.BYTE_TYPE), ClassName.create("java/lang/Byte"))
.put(ClassName.create(Type.SHORT_TYPE), ClassName.create("java/lang/Short"))
.put(ClassName.create(Type.INT_TYPE), ClassName.create("java/lang/Integer"))
.put(ClassName.create(Type.FLOAT_TYPE), ClassName.create("java/lang/Float"))
.put(ClassName.create(Type.LONG_TYPE), ClassName.create("java/lang/Long"))
.put(ClassName.create(Type.DOUBLE_TYPE), ClassName.create("java/lang/Double"))
.build();
private static final ImmutableBiMap<String, String> SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS =
ImmutableBiMap.<String, String>builder().put("javadesugar/", "jd$/").build();
public static final TypeMapper SHADOWED_TO_MIRRORED_TYPE_MAPPER =
new TypeMapper(ClassName::shadowedToMirrored);
public static final TypeMapper IMMUTABLE_LABEL_ATTACHER =
new TypeMapper(ClassName::withCoreTypeImmutableLabel);
public static ClassName create(String binaryName) {
checkArgument(
!binaryName.contains("."),
"Expected a binary/internal class name ('/'-delimited) instead of a qualified name."
+ " Actual: (%s)",
binaryName);
return new AutoValue_ClassName(binaryName);
}
public static ClassName create(Class<?> clazz) {
return create(Type.getType(clazz));
}
public static ClassName create(Type asmType) {
return create(asmType.getInternalName());
}
public static ClassName fromClassFileName(String fileName) {
checkArgument(
fileName.endsWith(".class"), "Expected a class file (*.class). Actual: (%s).", fileName);
return ClassName.create(fileName.substring(0, fileName.length() - ".class".length()));
}
private static void checkPackagePrefixFormat(String prefix) {
checkArgument(
prefix.isEmpty() || prefix.endsWith("/"),
"Expected (%s) to be a package prefix of ending with '/'.",
prefix);
checkArgument(
!prefix.contains("."),
"Expected a '/'-delimited binary name instead of a '.'-delimited qualified name for %s",
prefix);
}
/**
* The textual binary name used to index the class name, as defined at,
* https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.2.1
*/
public abstract String binaryName();
public final Type toAsmObjectType() {
return isPrimitive() ? PRIMITIVES_TYPES.get(binaryName()) : Type.getObjectType(binaryName());
}
public final ClassName toBoxedType() {
checkState(isPrimitive(), "Expected a primitive type for type boxing, but got %s", this);
return PRIMITIVES_TO_BOXED_TYPES.get(this);
}
public final boolean isPrimitive() {
return PRIMITIVES_TYPES.containsKey(binaryName());
}
public final boolean isWideType() {
return "D".equals(binaryName()) || "J".equals(binaryName());
}
public final boolean isBoxedType() {
return PRIMITIVES_TO_BOXED_TYPES.containsValue(this);
}
public final String qualifiedName() {
return binaryName().replace('/', '.');
}
public ClassName innerClass(String innerClassSimpleName) {
return ClassName.create(binaryName() + '$' + innerClassSimpleName);
}
public final String getPackageName() {
String binaryName = binaryName();
int i = binaryName.lastIndexOf('/');
return i < 0 ? "" : binaryName.substring(0, i + 1);
}
public final String simpleName() {
String binaryName = binaryName();
int i = binaryName.lastIndexOf('/');
return i < 0 ? binaryName : binaryName.substring(i + 1);
}
public final ClassName withSimpleNameSuffix(String suffix) {
return ClassName.create(binaryName() + suffix);
}
public final String classFilePathName() {
return binaryName() + ".class";
}
public final boolean hasInProcessLabel() {
return hasPackagePrefix(IN_PROCESS_LABEL);
}
public final boolean hasImmutableLabel() {
return hasPackagePrefix(IMMUTABLE_LABEL_LABEL);
}
/**
* Returns a new instance of {@code ClassName} that represents the owner class with adapter
* methods for an Android SDK APIs.
*/
public final ClassName typeAdapterOwner() {
checkState(
!hasInProcessLabel() && !hasImmutableLabel(),
"Expected a label-free type: Actual(%s)",
this);
checkState(
isInPackageEligibleForTypeAdapter(),
"Expected an Android SDK type to have an adapter: Actual (%s)",
this);
return withSimpleNameSuffix(TYPE_ADAPTER_SUFFIX).withPackagePrefix(TYPE_ADAPTER_PACKAGE_ROOT);
}
/**
* Returns a new instance of {@code ClassName} that represents the owner class with conversion
* methods between JDK built-in types and desguar-mirrored types.
*/
public final ClassName typeConverterOwner() {
checkState(
!hasInProcessLabel() && !hasImmutableLabel(),
"Expected a label-free type: Actual(%s)",
this);
checkState(
isDesugarShadowedType(),
"Expected an JDK built-in type to have an converter: Actual (%s)",
this);
return withSimpleNameSuffix(TYPE_CONVERTER_SUFFIX).withPackagePrefix(TYPE_ADAPTER_PACKAGE_ROOT);
}
/**
* Returns a new instance of {@code ClassName} attached with an immutable label which marks the
* type is not subject to further desugar operations until the final label striping.
*/
public final ClassName withCoreTypeImmutableLabel() {
return isDesugarShadowedType() ? withPackagePrefix(IMMUTABLE_LABEL_LABEL) : this;
}
/**
* Returns a new instance of {@code ClassName} that is the desugar-mirrored core type (e.g. {@code
* j$/time/MonthDay}) of the current shadowed built-in core type, assuming {@code this} instance
* is a desugared-shadowed built-in core type.
*/
public final ClassName shadowedToMirrored() {
return SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.keySet().stream()
.filter(this::hasPackagePrefix)
.map(
prefix ->
replacePackagePrefix(prefix, SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.get(prefix)))
.findAny()
.orElse(this);
}
/**
* Returns a new instance of {@code ClassName} that is a shadowed built-in core type (e.g. {@code
* java/time/MonthDay}) of the current desugar-mirrored core type, assuming {@code this} instance
* is a desugar-mirrored core type.
*/
public final ClassName mirroredToShadowed() {
ImmutableBiMap<String, String> verbatimTypeMappings =
SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.inverse();
return verbatimTypeMappings.keySet().stream()
.filter(this::hasPackagePrefix)
.map(prefix -> replacePackagePrefix(prefix, verbatimTypeMappings.get(prefix)))
.findAny()
.orElse(this);
}
public final ClassName withPackagePrefix(String prefix) {
checkPackagePrefixFormat(prefix);
return ClassName.create(prefix + binaryName());
}
public final boolean hasPackagePrefix(String prefix) {
return binaryName().startsWith(prefix);
}
public final boolean hasAnyPackagePrefix(String... prefixes) {
return Arrays.stream(prefixes).anyMatch(this::hasPackagePrefix);
}
public final boolean hasAnyPackagePrefix(Collection<String> prefixes) {
return prefixes.stream().anyMatch(this::hasPackagePrefix);
}
public final boolean isDesugarEligible(boolean enableDesugarBuiltinJdk) {
if (isDesugarShadowedType()) {
return enableDesugarBuiltinJdk;
}
return !isInPackageEligibleForTypeAdapter()
&& !isInDesugarRuntimeLibrary()
&& !isDesugarMirroredType();
}
public final boolean isInPackageEligibleForTypeAdapter() {
// TODO(b/152573900): Update to hasPackagePrefix("android/") once all package-wise incremental
// rollouts are complete.
return hasAnyPackagePrefix("android/testing/");
}
public final boolean isInDesugarRuntimeLibrary() {
return hasAnyPackagePrefix(
"com/google/devtools/build/android/desugar/runtime/", "desugar/runtime/");
}
public final boolean isDesugarShadowedType() {
return hasAnyPackagePrefix(SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.keySet());
}
public final boolean isDesugarMirroredType() {
return hasAnyPackagePrefix(SHADOWED_MIRRORED_TYPE_PREFIX_MAPPINGS.values());
}
private ClassName stripPackagePrefix(String prefix) {
return hasPackagePrefix(prefix) ? stripRequiredPackagePrefix(prefix) : this;
}
private ClassName stripRequiredPackagePrefix(String prefix) {
return replacePackagePrefix(/* originalPrefix= */ prefix, /* targetPrefix= */ "");
}
private ClassName replacePackagePrefix(String originalPrefix, String targetPrefix) {
checkState(
hasPackagePrefix(originalPrefix),
"Expected %s to have a package prefix of (%s) before stripping.",
this,
originalPrefix);
checkPackagePrefixFormat(targetPrefix);
return ClassName.create(targetPrefix + binaryName().substring(originalPrefix.length()));
}
@Override
public ClassName acceptTypeMapper(TypeMapper typeMapper) {
return typeMapper.map(this);
}
}