blob: 9d983f28c6dc17630a9f2c254878c9046977d6f5 [file] [log] [blame]
// Copyright 2019 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.nest;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.devtools.build.android.desugar.langmodel.LangModelConstants.NEST_COMPANION_CLASS_SIMPLE_NAME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import com.google.devtools.build.android.desugar.io.FileContentProvider;
import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord;
import com.google.devtools.build.android.desugar.langmodel.ClassMemberKey;
import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord;
import com.google.devtools.build.android.desugar.langmodel.ClassName;
import com.google.devtools.build.android.desugar.langmodel.MemberUseKind;
import com.google.devtools.build.android.desugar.langmodel.TypeMappable;
import com.google.devtools.build.android.desugar.langmodel.TypeMapper;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassWriter;
/** Manages the creation and IO stream for nest-companion classes. */
public class NestDigest implements TypeMappable<NestDigest> {
private final ClassMemberRecord classMemberRecord;
private final ClassAttributeRecord classAttributeRecord;
private final Map<ClassName, ClassName> nestCompanionToHostMap;
/**
* A map from the class binary names of nest hosts to the associated class writer of the nest's
* companion.
*/
private ImmutableMap<ClassName, ClassWriter> companionWriters;
public static NestDigest create(
ClassMemberRecord classMemberRecord, ClassAttributeRecord classAttributeRecord) {
return new NestDigest(
classMemberRecord, classAttributeRecord, new HashMap<>(), /* companionWriters= */ null);
}
private NestDigest(
ClassMemberRecord classMemberRecord,
ClassAttributeRecord classAttributeRecord,
Map<ClassName, ClassName> nestCompanionToHostMap,
ImmutableMap<ClassName, ClassWriter> companionWriters) {
this.classMemberRecord = classMemberRecord;
this.classAttributeRecord = classAttributeRecord;
this.nestCompanionToHostMap = nestCompanionToHostMap;
this.companionWriters = companionWriters;
}
/**
* Generates the nest companion class writers. The nest companion classes will be generated as the
* last placeholder class type for the synthetic constructor, whose originating constructor has
* any invocation in other classes in nest.
*/
void prepareCompanionClasses() {
ImmutableList<ClassName> nestHostsWithCompanion =
classMemberRecord.findAllConstructorMemberKeys().stream()
.map(
constructor ->
nestHost(constructor.owner(), classAttributeRecord, nestCompanionToHostMap))
.flatMap(Streams::stream)
.distinct()
.collect(toImmutableList());
ImmutableMap.Builder<ClassName, ClassWriter> companionWriterBuilders = ImmutableMap.builder();
for (ClassName nestHost : nestHostsWithCompanion) {
ClassName nestCompanion = nestHost.innerClass(NEST_COMPANION_CLASS_SIMPLE_NAME);
nestCompanionToHostMap.put(nestCompanion, nestHost);
companionWriterBuilders.put(nestHost, new ClassWriter(ClassWriter.COMPUTE_MAXS));
}
companionWriters = companionWriterBuilders.build();
}
public boolean hasAnyTrackingReason(ClassMemberKey<?> classMemberKey) {
return classMemberRecord.hasTrackingReason(classMemberKey);
}
public boolean hasAnyUse(ClassMemberKey<?> classMemberKey, MemberUseKind useKind) {
return findAllMemberUseKinds(classMemberKey).contains(useKind);
}
public ImmutableList<MemberUseKind> findAllMemberUseKinds(ClassMemberKey<?> classMemberKey) {
return classMemberRecord.findAllMemberUseKind(classMemberKey);
}
/**
* The public API that finds the nest host for a given class. It is expected {@link
* #prepareCompanionClasses()} executed before this API is ready. The method returns {@link
* Optional#empty()} if the class is not part of a nest. A generated nest companion class and its
* nest host are considered to be a nest host/member relationship.
*/
public Optional<ClassName> nestHost(ClassName className) {
// Ensures prepareCompanionClasses has been executed.
checkNotNull(companionWriters);
return nestHost(className, classAttributeRecord, nestCompanionToHostMap);
}
/**
* The internal method finds the nest host for a given class from a class attribute record. The
* method returns {@link * Optional#empty()} if the class is not part of a nest. A generated nest
* companion class and its * nest host are considered to be a nest host/member relationship.
*
* <p>In addition to exam the NestHost_attribute from the class file, this method returns the
* class under investigation itself for a class with NestMembers_attribute but without
* NestHost_attribute.
*/
private static Optional<ClassName> nestHost(
ClassName className,
ClassAttributeRecord classAttributeRecord,
Map<ClassName, ClassName> companionToHostMap) {
if (companionToHostMap.containsKey(className)) {
return Optional.of(companionToHostMap.get(className));
}
Optional<ClassName> nestHost = classAttributeRecord.getNestHost(className);
if (nestHost.isPresent()) {
return nestHost;
}
Set<ClassName> nestMembers = classAttributeRecord.getNestMembers(className);
if (!nestMembers.isEmpty()) {
return Optional.of(className);
}
return Optional.empty();
}
/**
* Returns the internal name of the nest companion class for a given class.
*
* <p>e.g. The nest host of a/b/C$D is a/b/C$NestCC
*/
public ClassName nestCompanion(ClassName className) {
return nestHost(className)
.map(nestHost -> nestHost.innerClass(NEST_COMPANION_CLASS_SIMPLE_NAME))
.orElseThrow(
() ->
new IllegalStateException(
String.format(
"Expected the presence of NestHost attribute of %s to get nest companion.",
className)));
}
/**
* Gets the class visitor of the affiliated nest host of the given class. E.g For the given class
* com/google/a/b/Delta$Echo, it returns the class visitor of com/google/a/b/Delta$NestCC
*/
@Nullable
public ClassWriter getCompanionClassWriter(ClassName className) {
return nestHost(className).map(nestHost -> companionWriters.get(nestHost)).orElse(null);
}
/** Gets all nest companion classes required to be generated. */
public ImmutableList<String> getAllCompanionClassNames() {
return getAllCompanionClasses().stream().map(ClassName::binaryName).collect(toImmutableList());
}
public ImmutableList<ClassName> getAllCompanionClasses() {
return companionWriters.keySet().stream().map(this::nestCompanion).collect(toImmutableList());
}
/** Gets all nest companion files required to be generated. */
public ImmutableList<FileContentProvider<ByteArrayInputStream>> getCompanionFileProviders() {
ImmutableList.Builder<FileContentProvider<ByteArrayInputStream>> fileContents =
ImmutableList.builder();
for (ClassName companion : getAllCompanionClasses()) {
fileContents.add(
new FileContentProvider<>(
companion.classFilePathName(),
() -> getByteArrayInputStreamOfCompanionClass(companion)));
}
return fileContents.build();
}
private ByteArrayInputStream getByteArrayInputStreamOfCompanionClass(ClassName companion) {
ClassWriter companionClassWriter = getCompanionClassWriter(companion);
companionClassWriter.visitEnd();
checkNotNull(
companionClassWriter,
"Expected companion class (%s) to be present in (%s)",
companionWriters);
return new ByteArrayInputStream(companionClassWriter.toByteArray());
}
@Override
public NestDigest acceptTypeMapper(TypeMapper typeMapper) {
return new NestDigest(
classMemberRecord.acceptTypeMapper(typeMapper),
classAttributeRecord.acceptTypeMapper(typeMapper),
typeMapper.mapMutable(nestCompanionToHostMap),
typeMapper.mapKey(companionWriters));
}
}