| // 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.javaFormatRawOutput(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)); |
| } |
| } |