blob: b6a1c72f4f7ddaec9d02aae1657dddc5f28b3dfe [file] [log] [blame]
// Copyright (c) 2020, 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.androidapi;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class GenerateAvailableApiExceptions {
private static final int fixedApiLevel = AndroidApiLevel.L.getLevel();
private static String generateBuildMapCode(Path apiVersionsXml) throws Exception {
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(fixedApiLevel));
Map<DexClass, Boolean> isExceptionCache = new IdentityHashMap<>();
Int2ReferenceMap<Set<String>> exceptionsMap = new Int2ReferenceOpenHashMap<>();
// Read api-versions.xml locating all classes that derive throwable and adding them to the map.
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document document = factory.newDocumentBuilder().parse(apiVersionsXml.toFile());
NodeList classes = document.getElementsByTagName("class");
int totalCount = 0;
for (int i = 0; i < classes.getLength(); i++) {
Node node = classes.item(i);
assert node.getNodeType() == Node.ELEMENT_NODE;
Node sinceAttr = node.getAttributes().getNamedItem("since");
if (sinceAttr == null) {
continue;
}
int since = Integer.parseInt(sinceAttr.getNodeValue());
if (since >= fixedApiLevel) {
continue;
}
String name = node.getAttributes().getNamedItem("name").getNodeValue();
String type = DescriptorUtils.getJavaTypeFromBinaryName(name);
ClassSubject clazz = inspector.clazz(type);
if (isThrowable(clazz, isExceptionCache, inspector)) {
exceptionsMap.computeIfAbsent(since, k -> new TreeSet<>()).add(name);
totalCount++;
}
}
// Build the code for the buildMap method.
StringBuilder builder = new StringBuilder();
builder.append("public class DoNotCommit {");
builder.append("public static Set<DexType> build(DexItemFactory factory, int minApiLevel) {");
builder.append(" Set<DexType> types = SetUtils.newIdentityHashSet(" + totalCount + ");");
for (int api = 1; api < fixedApiLevel; api++) {
Set<String> names = exceptionsMap.get(api);
if (names == null || names.isEmpty()) {
continue;
}
builder.append("if (minApiLevel >= " + api + ") {");
for (String name : names) {
String desc = DescriptorUtils.getDescriptorFromClassBinaryName(name);
builder.append(" types.add(factory.createType(\"" + desc + "\"));");
}
builder.append("}");
}
builder.append(" return types;");
builder.append("}");
builder.append("}");
// Write to temp file and format that file.
String rawOutput = builder.toString();
Path tempFile = Files.createTempFile("output-", ".java");
FileUtils.writeTextFile(tempFile, rawOutput);
return MethodGenerationBase.formatRawOutput(tempFile);
}
private static boolean isThrowable(
ClassSubject clazz, Map<DexClass, Boolean> cache, CodeInspector inspector) {
if (!clazz.isPresent()) {
return false;
}
if (clazz.getDexProgramClass().type == inspector.getFactory().objectType) {
return false;
}
if (clazz.getDexProgramClass().type == inspector.getFactory().throwableType) {
return true;
}
return cache.computeIfAbsent(
clazz.getDexProgramClass(),
c -> {
return c.superType != null
&& isThrowable(inspector.clazz(c.superType.toSourceString()), cache, inspector);
});
}
public static void main(String[] args) throws Exception {
Path apiVersionsXml =
args.length == 1
? Paths.get(args[0])
: Paths.get("<your-android-checkout>/development/sdk/api-versions.xml");
System.out.println(generateBuildMapCode(apiVersionsXml));
}
}