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