/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apigenerator; import com.android.utils.Pair; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}. * */ public class AndroidJarReader { private static final byte[] BUFFER = new byte[65535]; private final int mMinApi; private final ArrayList<String> mPatterns; public AndroidJarReader(ArrayList<String> patterns, int minApi) { mPatterns = patterns; mMinApi = minApi; } public Map<String, ApiClass> getClasses() { HashMap<String, ApiClass> map = new HashMap<String, ApiClass>(); // Get all the android.jar. They are in platforms-# int apiLevel = mMinApi - 1; while (true) { apiLevel++; try { File jar = getAndroidJarFile(apiLevel); if (jar == null || !jar.isFile()) { System.out.println("Last API level found: " + (apiLevel-1)); break; } System.out.println("Found API " + apiLevel + " at " + jar.getPath()); FileInputStream fis = new FileInputStream(jar); ZipInputStream zis = new ZipInputStream(fis); ZipEntry entry = zis.getNextEntry(); while (entry != null) { String name = entry.getName(); if (name.endsWith(".class")) { int index = 0; do { int size = zis.read(BUFFER, index, BUFFER.length - index); if (size >= 0) { index += size; } else { break; } } while (true); byte[] b = new byte[index]; System.arraycopy(BUFFER, 0, b, 0, index); ClassReader reader = new ClassReader(b); ClassNode classNode = new ClassNode(); reader.accept(classNode, 0 /*flags*/); ApiClass theClass = addClass(map, classNode.name, apiLevel, (classNode.access & Opcodes.ACC_DEPRECATED) != 0); // super class if (classNode.superName != null) { theClass.addSuperClass(classNode.superName, apiLevel); } // interfaces for (Object interfaceName : classNode.interfaces) { theClass.addInterface((String) interfaceName, apiLevel); } // fields for (Object field : classNode.fields) { FieldNode fieldNode = (FieldNode) field; if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) { continue; } if (!fieldNode.name.startsWith("this$") && !fieldNode.name.equals("$VALUES")) { boolean deprecated = (fieldNode.access & Opcodes.ACC_DEPRECATED) != 0; theClass.addField(fieldNode.name, apiLevel, deprecated); } } // methods for (Object method : classNode.methods) { MethodNode methodNode = (MethodNode) method; if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) { continue; } if (!methodNode.name.equals("<clinit>")) { boolean deprecated = (methodNode.access & Opcodes.ACC_DEPRECATED) != 0; theClass.addMethod(methodNode.name + methodNode.desc, apiLevel, deprecated); } } } entry = zis.getNextEntry(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { } } postProcessClasses(map); return map; } private File getAndroidJarFile(int apiLevel) { for (String pattern : mPatterns) { File f = new File(pattern.replace("%", Integer.toString(apiLevel))); if (f.isFile()) { return f; } } return null; } private void postProcessClasses(Map<String, ApiClass> classes) { for (ApiClass theClass : classes.values()) { Map<String, Integer> methods = theClass.getMethods(); Map<String, Integer> fixedMethods = new HashMap<String, Integer>(); List<Pair<String, Integer>> superClasses = theClass.getSuperClasses(); List<Pair<String, Integer>> interfaces = theClass.getInterfaces(); methodLoop: for (Entry<String, Integer> method : methods.entrySet()) { String methodName = method.getKey(); int apiLevel = method.getValue(); if (!methodName.startsWith("<init>(")) { for (Pair<String, Integer> parent : superClasses) { // only check the parent if it was a parent class at the introduction // of the method. if (parent.getSecond() <= apiLevel) { ApiClass parentClass = classes.get(parent.getFirst()); assert parentClass != null; if (parentClass != null && checkClassContains(theClass.getName(), methodName, apiLevel, classes, parentClass)) { continue methodLoop; } } } for (Pair<String, Integer> parent : interfaces) { // only check the parent if it was a parent class at the introduction // of the method. if (parent.getSecond() <= apiLevel) { ApiClass parentClass = classes.get(parent.getFirst()); assert parentClass != null; if (parentClass != null && checkClassContains(theClass.getName(), methodName, apiLevel, classes, parentClass)) { continue methodLoop; } } } } // if we reach here. the method isn't an override fixedMethods.put(methodName, method.getValue()); } theClass.replaceMethods(fixedMethods); } } private boolean checkClassContains(String className, String methodName, int apiLevel, Map<String, ApiClass> classMap, ApiClass parentClass) { Integer parentMethodApiLevel = parentClass.getMethods().get(methodName); if (parentMethodApiLevel != null && parentMethodApiLevel <= apiLevel) { // the parent class has the method and it was introduced in the parent at the // same api level as the method, or before. return true; } // check on this class parents. List<Pair<String, Integer>> superClasses = parentClass.getSuperClasses(); List<Pair<String, Integer>> interfaces = parentClass.getInterfaces(); for (Pair<String, Integer> parent : superClasses) { // only check the parent if it was a parent class at the introduction // of the method. if (parent.getSecond() <= apiLevel) { ApiClass superParentClass = classMap.get(parent.getFirst()); assert superParentClass != null; if (superParentClass != null && checkClassContains(className, methodName, apiLevel, classMap, superParentClass)) { return true; } } } for (Pair<String, Integer> parent : interfaces) { // only check the parent if it was a parent class at the introduction // of the method. if (parent.getSecond() <= apiLevel) { ApiClass superParentClass = classMap.get(parent.getFirst()); assert superParentClass != null; if (superParentClass != null && checkClassContains(className, methodName, apiLevel, classMap, superParentClass)) { return true; } } } return false; } private ApiClass addClass(HashMap<String, ApiClass> classes, String name, int apiLevel, boolean deprecated) { ApiClass theClass = classes.get(name); if (theClass == null) { theClass = new ApiClass(name, apiLevel); classes.put(name, theClass); } if (deprecated && apiLevel < theClass.deprecatedIn) { theClass.deprecatedIn = apiLevel; } return theClass; } }