1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bedstead.remoteframeworkclasses.processor; 18 19 import com.google.common.collect.ImmutableSet; 20 import com.google.common.io.Resources; 21 22 import java.io.IOException; 23 import java.nio.charset.StandardCharsets; 24 import java.util.Arrays; 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Map; 28 import java.util.Set; 29 import java.util.stream.Collectors; 30 31 import javax.lang.model.element.TypeElement; 32 import javax.lang.model.type.TypeKind; 33 import javax.lang.model.type.TypeMirror; 34 import javax.lang.model.util.Elements; 35 import javax.lang.model.util.Types; 36 37 /** 38 * A collection of {@link MethodSignature} for accessible methods. 39 */ 40 public final class Apis { 41 42 private static final String[] API_FILES = 43 {"current.txt", "wifi-current.txt", "bluetooth-current.txt", "system-current.txt"}; 44 45 private static final Map<String, String> API_TXTS = initialiseApiTxts(); 46 private static final Map<String, Apis> sPackageToApi = new HashMap<>(); 47 initialiseApiTxts()48 private static Map<String, String> initialiseApiTxts() { 49 return Arrays.stream(API_FILES) 50 .collect(Collectors.toMap(f -> f, f -> { 51 try { 52 return Resources.toString(Processor.class.getResource("/apis/" + f), 53 StandardCharsets.UTF_8); 54 } catch (IOException e) { 55 throw new IllegalStateException("Could not read file " + f); 56 } 57 })); 58 } 59 60 /** 61 * Get public and test APIs for a given class name. 62 */ 63 public static Apis forClass(String className, Types types, Elements elements) { 64 if (sPackageToApi.containsKey(className)) { 65 return sPackageToApi.get(className); 66 } 67 ImmutableSet.Builder<MethodSignature> methods = ImmutableSet.builder(); 68 Set<String> parents = new HashSet<>(); 69 findParents(parents, className, elements); 70 for (String c : parents) { 71 for (Map.Entry<String, String> apiTxt : API_TXTS.entrySet()) { 72 methods.addAll( 73 parseApiTxt(apiTxt.getKey(), apiTxt.getValue(), c, types, elements)); 74 } 75 } 76 77 return new Apis(methods.build()); 78 } 79 80 private static void findParents(Set<String> parents, String className, Elements elements) { 81 parents.add(className); 82 83 if (className.equals("java.lang.Object")) { 84 return; 85 } 86 87 TypeElement element = elements.getTypeElement(className); 88 System.out.println("Checking " + className + " got " + element); 89 90 TypeMirror superClass = element.getSuperclass(); 91 if (!superClass.getKind().equals(TypeKind.NONE)) { 92 findParents(parents, superClass.toString(), elements); 93 } 94 95 element.getInterfaces().stream().map(TypeMirror::toString) 96 .forEach(c -> findParents(parents, c, elements)); 97 } 98 99 private static Set<MethodSignature> parseApiTxt( 100 String filename, String apiTxt, String className, Types types, Elements elements) { 101 System.out.println("Parsing for " + className); 102 103 int separatorPosition = className.lastIndexOf("."); 104 String packageName = className.substring(0, separatorPosition); 105 String simpleClassName = className.substring(separatorPosition + 1); 106 107 String[] packageSplit = apiTxt.split("package " + packageName + " \\{", 2); 108 if (packageSplit.length < 2) { 109 System.out.println("Package " + packageName + " not in file " + filename); 110 // Package not in this file 111 return new HashSet<>(); 112 } 113 String[] classSplit = packageSplit[1].split("class " + simpleClassName + " .*?\n", 2); 114 if (classSplit.length < 2) { 115 System.out.println("Class " + simpleClassName + " not in file " + filename); 116 // Class not in this file 117 return new HashSet<>(); 118 } 119 String[] lines = classSplit[1].split("\n"); 120 Set<MethodSignature> methodSignatures = new HashSet<>(); 121 122 for (String line : lines) { 123 String methodLine = line.trim(); 124 if (methodLine.isEmpty()) { 125 continue; 126 } 127 128 if (methodLine.startsWith("ctor")) { 129 // Skip constructors 130 continue; 131 } 132 133 if (!methodLine.startsWith("method")) { 134 return methodSignatures; 135 } 136 137 try { 138 // Strip "method" and semicolon 139 methodLine = methodLine.substring(7, methodLine.length() - 1); 140 MethodSignature signature = 141 MethodSignature.forApiString(methodLine, types, elements); 142 if (signature != null) { 143 methodSignatures.add(signature); 144 } 145 } catch (RuntimeException e) { 146 throw new IllegalStateException("Error parsing method " + line, e); 147 } 148 } 149 150 return methodSignatures; 151 } 152 153 private final ImmutableSet<MethodSignature> mMethods; 154 155 private Apis(ImmutableSet<MethodSignature> methods) { 156 mMethods = methods; 157 } 158 159 /** 160 * Get methods in the API set. 161 */ 162 public ImmutableSet<MethodSignature> methods() { 163 return mMethods; 164 } 165 } 166