blob: 75fa1c58e529d27c09da68ca7aa68b3641d61c47 [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 TYPE_ADAPTER_PACKAGE_ROOT = "desugar/runtime/typeadapter/";
/**
* 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> DELIVERY_TYPE_MAPPINGS =
ImmutableBiMap.<String, String>builder()
.put("java/", "j$/")
.put("javadesugar/", "jd$/")
.build();
public static final TypeMapper IN_PROCESS_LABEL_STRIPPER =
new TypeMapper(ClassName::verbatimName);
public static final TypeMapper DELIVERY_TYPE_MAPPER =
new TypeMapper(ClassName::verbatimToDelivery);
public static final TypeMapper VERBATIM_TYPE_MAPPER =
new TypeMapper(ClassName::deliveryToVerbatim);
/**
* 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 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 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);
}
private ClassName stripInProcessLabel() {
return stripPackagePrefix(IN_PROCESS_LABEL);
}
private ClassName stripInProcessLabelIfAny() {
return hasInProcessLabel() ? stripPackagePrefix(IN_PROCESS_LABEL) : this;
}
/** Strips out in-process labels if any. */
public final ClassName verbatimName() {
return stripInProcessLabelIfAny().deliveryToVerbatim();
}
public final ClassName typeAdapterOwner() {
return verbatimName().withSimpleNameSuffix("Adapter").prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
}
public final ClassName typeConverterOwner() {
return verbatimName()
.withSimpleNameSuffix("Converter")
.prependPrefix(TYPE_ADAPTER_PACKAGE_ROOT);
}
public final ClassName verbatimToDelivery() {
return DELIVERY_TYPE_MAPPINGS.keySet().stream()
.filter(this::hasPackagePrefix)
.map(prefix -> replacePackagePrefix(prefix, DELIVERY_TYPE_MAPPINGS.get(prefix)))
.findAny()
.orElse(this);
}
public final ClassName deliveryToVerbatim() {
ImmutableBiMap<String, String> verbatimTypeMappings = DELIVERY_TYPE_MAPPINGS.inverse();
return verbatimTypeMappings.keySet().stream()
.filter(this::hasPackagePrefix)
.map(prefix -> replacePackagePrefix(prefix, verbatimTypeMappings.get(prefix)))
.findAny()
.orElse(this);
}
public final ClassName prependPrefix(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 ClassName stripPackagePrefix(String prefix) {
return replacePackagePrefix(/* originalPrefix= */ prefix, /* targetPrefix= */ "");
}
public final 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);
}
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);
}
public boolean isInProcessCoreType() {
return hasInProcessLabel() && stripInProcessLabel().isVerbatimCoreType();
}
public boolean isVerbatimCoreType() {
return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.keySet());
}
public boolean isDeliveryCoreType() {
return hasAnyPackagePrefix(DELIVERY_TYPE_MAPPINGS.values());
}
}