1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *      http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13
14package android.databinding.tool.reflection;
15
16import org.apache.commons.io.FileUtils;
17import org.apache.commons.io.IOUtils;
18import org.w3c.dom.Document;
19import org.w3c.dom.Node;
20import org.w3c.dom.NodeList;
21
22import android.databinding.tool.util.L;
23import android.databinding.tool.util.Preconditions;
24
25import java.io.File;
26import java.io.InputStream;
27import java.util.HashMap;
28import java.util.Map;
29
30import javax.xml.parsers.DocumentBuilder;
31import javax.xml.parsers.DocumentBuilderFactory;
32import javax.xml.xpath.XPath;
33import javax.xml.xpath.XPathExpressionException;
34import javax.xml.xpath.XPathFactory;
35
36/**
37 * Class that is used for SDK related stuff.
38 * <p>
39 * Must be initialized with the sdk location to work properly
40 */
41public class SdkUtil {
42
43    static ApiChecker sApiChecker;
44
45    static int sMinSdk;
46
47    public static void initialize(int minSdk, File sdkPath) {
48        sMinSdk = minSdk;
49        sApiChecker = new ApiChecker(new File(sdkPath.getAbsolutePath()
50                + "/platform-tools/api/api-versions.xml"));
51        L.d("SdkUtil init, minSdk: %s", minSdk);
52    }
53
54    public static int getMinApi(ModelClass modelClass) {
55        return sApiChecker.getMinApi(modelClass.getJniDescription(), null);
56    }
57
58    public static int getMinApi(ModelMethod modelMethod) {
59        ModelClass declaringClass = modelMethod.getDeclaringClass();
60        Preconditions.checkNotNull(sApiChecker, "should've initialized api checker");
61        while (declaringClass != null) {
62            String classDesc = declaringClass.getJniDescription();
63            String methodDesc = modelMethod.getJniDescription();
64            int result = sApiChecker.getMinApi(classDesc, methodDesc);
65            L.d("checking method api for %s, class:%s method:%s. result: %d", modelMethod.getName(),
66                    classDesc, methodDesc, result);
67            if (result > 0) {
68                return result;
69            }
70            declaringClass = declaringClass.getSuperclass();
71        }
72        return 1;
73    }
74
75    static class ApiChecker {
76
77        private Map<String, Integer> mFullLookup;
78
79        private Document mDoc;
80
81        private XPath mXPath;
82
83        public ApiChecker(File apiFile) {
84            InputStream inputStream = null;
85            try {
86                if (apiFile == null || !apiFile.exists()) {
87                    inputStream = getClass().getClassLoader().getResourceAsStream("api-versions.xml");
88                } else {
89                    inputStream = FileUtils.openInputStream(apiFile);
90                }
91                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
92                DocumentBuilder builder = factory.newDocumentBuilder();
93                mDoc = builder.parse(inputStream);
94                XPathFactory xPathFactory = XPathFactory.newInstance();
95                mXPath = xPathFactory.newXPath();
96                buildFullLookup();
97            } catch (Throwable t) {
98                L.e(t, "cannot load api descriptions from %s", apiFile);
99            } finally {
100                IOUtils.closeQuietly(inputStream);
101            }
102        }
103
104        private void buildFullLookup() throws XPathExpressionException {
105            NodeList allClasses = mDoc.getChildNodes().item(0).getChildNodes();
106            mFullLookup = new HashMap<String, Integer>(allClasses.getLength() * 4);
107            for (int j = 0; j < allClasses.getLength(); j++) {
108                Node node = allClasses.item(j);
109                if (node.getNodeType() != Node.ELEMENT_NODE || !"class"
110                        .equals(node.getNodeName())) {
111                    continue;
112                }
113                //L.d("checking node %s", node.getAttributes().getNamedItem("name").getNodeValue());
114                int classSince = getSince(node);
115                String classDesc = node.getAttributes().getNamedItem("name").getNodeValue();
116
117                final NodeList childNodes = node.getChildNodes();
118                for (int i = 0; i < childNodes.getLength(); i++) {
119                    Node child = childNodes.item(i);
120                    if (child.getNodeType() != Node.ELEMENT_NODE || !"method"
121                            .equals(child.getNodeName())) {
122                        continue;
123                    }
124                    int methodSince = getSince(child);
125                    int since = Math.max(classSince, methodSince);
126                    String methodDesc = child.getAttributes().getNamedItem("name")
127                            .getNodeValue();
128                    String key = cacheKey(classDesc, methodDesc);
129                    mFullLookup.put(key, since);
130                }
131            }
132        }
133
134        /**
135         * Returns 0 if we cannot find the API level for the method.
136         */
137        public int getMinApi(String classDesc, String methodOrFieldDesc) {
138            if (mDoc == null || mXPath == null) {
139                return 1;
140            }
141            if (classDesc == null || classDesc.isEmpty()) {
142                return 1;
143            }
144            final String key = cacheKey(classDesc, methodOrFieldDesc);
145            Integer since = mFullLookup.get(key);
146            return since == null ? 0 : since;
147        }
148
149        private static String cacheKey(String classDesc, String methodOrFieldDesc) {
150            return classDesc + "~" + methodOrFieldDesc;
151        }
152
153        private static int getSince(Node node) {
154            final Node since = node.getAttributes().getNamedItem("since");
155            if (since != null) {
156                final String nodeValue = since.getNodeValue();
157                if (nodeValue != null && !nodeValue.isEmpty()) {
158                    try {
159                        return Integer.parseInt(nodeValue);
160                    } catch (Throwable t) {
161                    }
162                }
163            }
164
165            return 1;
166        }
167    }
168}
169