blob: 4a3cfa2e115eef0c368ffa117a8e473f777f6c4e [file] [log] [blame]
// Copyright (c) 2021, 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.apimodel;
import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class AndroidApiVersionsXmlParser {
private final List<ParsedApiClass> classes = new ArrayList<>();
private final File apiVersionsXml;
private final AndroidApiLevel maxApiLevel;
private AndroidApiVersionsXmlParser(File apiVersionsXml, AndroidApiLevel maxApiLevel) {
this.apiVersionsXml = apiVersionsXml;
this.maxApiLevel = maxApiLevel;
}
private ParsedApiClass register(
ClassReference reference, AndroidApiLevel apiLevel, boolean isInterface) {
ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel, isInterface);
classes.add(parsedApiClass);
return parsedApiClass;
}
private void readApiVersionsXmlFile() throws Exception {
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(maxApiLevel));
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document document = factory.newDocumentBuilder().parse(apiVersionsXml);
NodeList classes = document.getElementsByTagName("class");
for (int i = 0; i < classes.getLength(); i++) {
Node node = classes.item(i);
assert node.getNodeType() == Node.ELEMENT_NODE;
AndroidApiLevel apiLevel = getMaxAndroidApiLevelFromNode(node, AndroidApiLevel.B);
String type = DescriptorUtils.getJavaTypeFromBinaryName(getName(node));
ClassSubject clazz = inspector.clazz(type);
if (!clazz.isPresent()) {
if (!clazz.getOriginalName().startsWith("android.test")
&& !clazz.getOriginalName().startsWith("junit")) {
assert hasRemoved(node);
assert getRemoved(node).isLessThanOrEqualTo(maxApiLevel);
}
continue;
}
ClassReference originalReference = clazz.getOriginalReference();
ParsedApiClass parsedApiClass = register(originalReference, apiLevel, clazz.isInterface());
NodeList members = node.getChildNodes();
for (int j = 0; j < members.getLength(); j++) {
Node memberNode = members.item(j);
if (isExtends(memberNode)) {
parsedApiClass.registerSuperType(
Reference.classFromBinaryName(getName(memberNode)),
hasSince(memberNode) ? getSince(memberNode) : apiLevel);
} else if (isImplements(memberNode)) {
parsedApiClass.registerInterface(
Reference.classFromBinaryName(getName(memberNode)),
hasSince(memberNode) ? getSince(memberNode) : apiLevel);
} else if (isMethod(memberNode)) {
parsedApiClass.register(
getMethodReference(originalReference, memberNode),
getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
} else if (isField(memberNode)) {
// The field do not have descriptors and are supposed to be unique.
FieldSubject fieldSubject = clazz.uniqueFieldWithName(getName(memberNode));
if (!fieldSubject.isPresent()) {
assert hasRemoved(memberNode);
assert getRemoved(memberNode).isLessThanOrEqualTo(maxApiLevel);
continue;
}
parsedApiClass.register(
fieldSubject.getOriginalReference(),
getMaxAndroidApiLevelFromNode(memberNode, apiLevel));
}
}
}
}
private boolean isMethod(Node node) {
return node.getNodeName().equals("method");
}
private String getName(Node node) {
return node.getAttributes().getNamedItem("name").getNodeValue();
}
private MethodReference getMethodReference(ClassReference classDescriptor, Node node) {
assert isMethod(node);
String name = getName(node);
int signatureStart = name.indexOf('(');
assert signatureStart > 0;
String parsedName = name.substring(0, signatureStart).replace("&lt;", "<");
assert !parsedName.contains("&");
return Reference.methodFromDescriptor(
classDescriptor.getDescriptor(), parsedName, name.substring(signatureStart));
}
private boolean isField(Node node) {
return node.getNodeName().equals("field");
}
private boolean isExtends(Node node) {
return node.getNodeName().equals("extends");
}
private boolean isImplements(Node node) {
return node.getNodeName().equals("implements");
}
private boolean hasSince(Node node) {
return node.getAttributes().getNamedItem("since") != null;
}
private boolean hasRemoved(Node node) {
return node.getAttributes().getNamedItem("removed") != null;
}
private AndroidApiLevel getSince(Node node) {
assert hasSince(node);
Node since = node.getAttributes().getNamedItem("since");
return AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue()));
}
private AndroidApiLevel getRemoved(Node node) {
assert hasRemoved(node);
Node removed = node.getAttributes().getNamedItem("removed");
return AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(removed.getNodeValue()));
}
private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
if (node == null || !hasSince(node)) {
return defaultValue;
}
return defaultValue.max(getSince(node));
}
public static List<ParsedApiClass> getParsedApiClasses(
File apiVersionsXml, AndroidApiLevel apiLevel) throws Exception {
AndroidApiVersionsXmlParser parser = new AndroidApiVersionsXmlParser(apiVersionsXml, apiLevel);
parser.readApiVersionsXmlFile();
return parser.classes;
}
public static class ParsedApiClass {
private final ClassReference classReference;
private final AndroidApiLevel apiLevel;
private final boolean isInterface;
private final Map<ClassReference, AndroidApiLevel> superTypes = new LinkedHashMap<>();
private final Map<ClassReference, AndroidApiLevel> interfaces = new LinkedHashMap<>();
private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>();
private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>();
private ParsedApiClass(
ClassReference classReference, AndroidApiLevel apiLevel, boolean isInterface) {
this.classReference = classReference;
this.apiLevel = apiLevel;
this.isInterface = isInterface;
}
public ClassReference getClassReference() {
return classReference;
}
public AndroidApiLevel getApiLevel() {
return apiLevel;
}
/**
* getMemberCount() returns the total number of members present on the max api level, that is it
* is the disjoint union of all members for all api levels.
*/
public int getTotalMemberCount() {
int count = 0;
for (List<FieldReference> value : fieldReferences.values()) {
count += value.size();
}
for (List<MethodReference> value : methodReferences.values()) {
count += value.size();
}
return count;
}
private void register(FieldReference reference, AndroidApiLevel apiLevel) {
fieldReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
}
private void register(MethodReference reference, AndroidApiLevel apiLevel) {
methodReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
}
private void registerSuperType(ClassReference superType, AndroidApiLevel apiLevel) {
AndroidApiLevel existing = superTypes.put(superType, apiLevel);
assert existing == null;
}
private void registerInterface(ClassReference iface, AndroidApiLevel apiLevel) {
AndroidApiLevel existing = interfaces.put(iface, apiLevel);
assert existing == null;
}
public void visitFieldReferences(BiConsumer<AndroidApiLevel, List<FieldReference>> consumer) {
fieldReferences.forEach(
(apiLevel, references) -> {
references.sort(Comparator.comparing(FieldReference::getFieldName));
consumer.accept(apiLevel, references);
});
}
public void visitMethodReferences(BiConsumer<AndroidApiLevel, List<MethodReference>> consumer) {
methodReferences.forEach(
(apiLevel, references) -> {
references.sort(Comparator.comparing(MethodReference::getMethodName));
consumer.accept(apiLevel, references);
});
}
public void visitSuperType(BiConsumer<ClassReference, AndroidApiLevel> consumer) {
superTypes.forEach(consumer);
}
public void visitInterface(BiConsumer<ClassReference, AndroidApiLevel> consumer) {
interfaces.forEach(consumer);
}
public boolean isInterface() {
return isInterface;
}
}
}