blob: ea71d6c4436d2abb0623d2adaf8a46931fd2d278 [file]
// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.apimodel;
import com.android.tools.r8.ApiDatabaseGeneratorException;
import com.android.tools.r8.apimodel.jar.ApiClassInfo;
import com.android.tools.r8.apimodel.jar.ApiJarInfo;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.MethodReference;
import java.util.ArrayList;
import java.util.Collection;
public class ParsedApiClassTrimming {
public enum SkipAnswer {
SKIP,
KEEP;
public static SkipAnswer skipIf(boolean conditionToSkip) {
return conditionToSkip ? SKIP : KEEP;
}
}
public interface Trimmer<E extends Throwable> {
SkipAnswer skipClass(ClassReference clazz, ApiRange range) throws E;
SkipAnswer skipExtends(ClassReference clazz, ClassReference supertype, ApiRange range) throws E;
SkipAnswer skipImplements(ClassReference clazz, ClassReference supertype, ApiRange range)
throws E;
SkipAnswer skipMethod(ClassReference clazz, MethodReference method, ApiRange range) throws E;
SkipAnswer skipField(ClassReference clazz, FieldTypelessReference field, ApiRange range)
throws E;
void done() throws E;
}
public static <E extends Throwable> Collection<ParsedApiClass> trim(
Collection<ParsedApiClass> classes, Trimmer<E> trimmer) throws E {
var result = new ArrayList<ParsedApiClass>();
for (var clazz : classes) {
if (trimmer.skipClass(clazz.getClassReference(), clazz.getRange()) == SkipAnswer.KEEP) {
result.add(trim(clazz, trimmer));
}
}
trimmer.done();
return result;
}
private static <E extends Throwable> ParsedApiClass trim(ParsedApiClass clazz, Trimmer<E> trimmer)
throws E {
var holdingClass = clazz.getClassReference();
var trimmedClass = new ParsedApiClass(holdingClass, clazz.getRange());
clazz.forEachSupertypeThrowing(
(classReference, apiRange) -> {
if (trimmer.skipExtends(holdingClass, classReference, apiRange) == SkipAnswer.KEEP) {
trimmedClass.registerSupertype(classReference, apiRange);
}
});
clazz.forEachInterfaceThrowing(
(classReference, apiRange) -> {
if (trimmer.skipImplements(holdingClass, classReference, apiRange) == SkipAnswer.KEEP) {
trimmedClass.registerInterface(classReference, apiRange);
}
});
clazz.forEachMethodThrowing(
(methodReference, apiRange) -> {
if (trimmer.skipMethod(holdingClass, methodReference, apiRange) == SkipAnswer.KEEP) {
trimmedClass.registerMethod(methodReference, apiRange);
}
});
clazz.forEachFieldThrowing(
(fieldReference, apiRange) -> {
if (trimmer.skipField(holdingClass, fieldReference, apiRange) == SkipAnswer.KEEP) {
trimmedClass.registerField(fieldReference, apiRange);
}
});
return trimmedClass;
}
public static class RemovedTrimmer implements Trimmer<RuntimeException> {
@Override
public SkipAnswer skipClass(ClassReference clazz, ApiRange range) {
return range.isRemoved() ? SkipAnswer.SKIP : SkipAnswer.KEEP;
}
@Override
public SkipAnswer skipExtends(ClassReference clazz, ClassReference supertype, ApiRange range) {
return range.isRemoved() ? SkipAnswer.SKIP : SkipAnswer.KEEP;
}
@Override
public SkipAnswer skipImplements(
ClassReference clazz, ClassReference supertype, ApiRange range) {
return range.isRemoved() ? SkipAnswer.SKIP : SkipAnswer.KEEP;
}
@Override
public SkipAnswer skipMethod(ClassReference clazz, MethodReference method, ApiRange range) {
return range.isRemoved() ? SkipAnswer.SKIP : SkipAnswer.KEEP;
}
@Override
public SkipAnswer skipField(
ClassReference clazz, FieldTypelessReference field, ApiRange range) {
return range.isRemoved() ? SkipAnswer.SKIP : SkipAnswer.KEEP;
}
@Override
public void done() throws RuntimeException {
// Do nothing.
}
}
public static class JarTrimmer
implements ParsedApiClassTrimming.Trimmer<ApiDatabaseGeneratorException> {
private final ApiJarInfo jarInfo;
public JarTrimmer(ApiJarInfo jarInfo) {
this.jarInfo = jarInfo;
}
public ApiJarInfo getJarInfo() {
return jarInfo;
}
@Override
public SkipAnswer skipClass(ClassReference clazz, ApiRange range) {
return SkipAnswer.skipIf(!jarInfo.hasClass(clazz));
}
@Override
public SkipAnswer skipExtends(ClassReference clazz, ClassReference supertype, ApiRange range) {
ApiClassInfo info = jarInfo.getClassInfo(clazz);
if (info == null) {
return SkipAnswer.SKIP;
}
if (info.getSuperClass() != null && info.getSuperClass().equals(supertype.getBinaryName())) {
return SkipAnswer.KEEP;
}
return SkipAnswer.SKIP;
}
@Override
public SkipAnswer skipImplements(
ClassReference clazz, ClassReference supertype, ApiRange range) {
ApiClassInfo info = jarInfo.getClassInfo(clazz);
if (info == null) {
return SkipAnswer.SKIP;
}
if (info.implementsInterface(supertype.getBinaryName())) {
return SkipAnswer.KEEP;
}
return SkipAnswer.SKIP;
}
@Override
public SkipAnswer skipMethod(ClassReference clazz, MethodReference method, ApiRange range)
throws ApiDatabaseGeneratorException {
return SkipAnswer.skipIf(
!jarInfo.hasMethod(
clazz.getBinaryName(), method.getMethodName(), method.getMethodDescriptor()));
}
@Override
public SkipAnswer skipField(ClassReference clazz, FieldTypelessReference field, ApiRange range)
throws ApiDatabaseGeneratorException {
return SkipAnswer.skipIf(!jarInfo.hasField(clazz.getBinaryName(), field.getFieldName()));
}
@Override
public void done() throws ApiDatabaseGeneratorException {
// Do nothing.
}
}
public interface TrimmerListener {
void skipClass(ClassReference clazz, ApiRange range, SkipAnswer answer);
void skipExtends(
ClassReference clazz, ClassReference supertype, ApiRange range, SkipAnswer answer);
void skipImplements(
ClassReference clazz, ClassReference supertype, ApiRange range, SkipAnswer answer);
void skipMethod(
ClassReference clazz, MethodReference method, ApiRange range, SkipAnswer answer);
void skipField(
ClassReference clazz, FieldTypelessReference field, ApiRange range, SkipAnswer answer);
void done();
}
public static class ListeningDecorator<E extends Throwable> implements Trimmer<E> {
private final Trimmer<E> trimmer;
private final TrimmerListener listener;
public ListeningDecorator(Trimmer<E> trimmer, TrimmerListener listener) {
this.trimmer = trimmer;
this.listener = listener;
}
@Override
public SkipAnswer skipClass(ClassReference clazz, ApiRange range) throws E {
var result = trimmer.skipClass(clazz, range);
listener.skipClass(clazz, range, result);
return result;
}
@Override
public SkipAnswer skipExtends(ClassReference clazz, ClassReference supertype, ApiRange range)
throws E {
var result = trimmer.skipExtends(clazz, supertype, range);
listener.skipExtends(clazz, supertype, range, result);
return result;
}
@Override
public SkipAnswer skipImplements(ClassReference clazz, ClassReference supertype, ApiRange range)
throws E {
var result = trimmer.skipImplements(clazz, supertype, range);
listener.skipImplements(clazz, supertype, range, result);
return result;
}
@Override
public SkipAnswer skipMethod(ClassReference clazz, MethodReference method, ApiRange range)
throws E {
var result = trimmer.skipMethod(clazz, method, range);
listener.skipMethod(clazz, method, range, result);
return result;
}
@Override
public SkipAnswer skipField(ClassReference clazz, FieldTypelessReference field, ApiRange range)
throws E {
var result = trimmer.skipField(clazz, field, range);
listener.skipField(clazz, field, range, result);
return result;
}
@Override
public void done() throws E {
trimmer.done();
listener.done();
}
}
}