1 /*
2  * Copyright (C) 2010 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.apkcheck;
18 
19 import org.xml.sax.*;
20 import org.xml.sax.helpers.*;
21 import java.io.*;
22 
23 
24 /**
25  * Provides implementation for SAX parser.
26  */
27 class ApiDescrHandler extends DefaultHandler {
28     /*
29      * Uber-container.
30      */
31     private ApiList mApiList;
32 
33     /*
34      * Temporary objects, used as containers while we accumulate the
35      * innards.
36      */
37     private PackageInfo mCurrentPackage = null;
38     private ClassInfo mCurrentClass = null;
39     private MethodInfo mCurrentMethod = null;
40 
41     /**
42      * Constructs an ApiDescrHandler.
43      *
44      * @param fileName Source file name, used for debugging.
45      */
ApiDescrHandler(ApiList apiList)46     public ApiDescrHandler(ApiList apiList) {
47         mApiList = apiList;
48     }
49 
50     /**
51      * Returns the ApiList in its current state.  Generally only
52      * makes sense to call here after parsing is completed.
53      */
getApiList()54     public ApiList getApiList() {
55         return mApiList;
56     }
57 
58     /**
59      * Processes start tags.  If the file is malformed we will likely
60      * NPE, but this is captured by the caller.
61      *
62      * We currently assume that packages and classes only appear once,
63      * so all classes associated with a package are wrapped in a singular
64      * instance of <package>.  We may want to remove this assumption
65      * by attempting to find an existing package/class with the same name.
66      */
67     @Override
startElement(String uri, String localName, String qName, Attributes attributes)68     public void startElement(String uri, String localName, String qName,
69             Attributes attributes) {
70 
71         if (qName.equals("package")) {
72             /* top-most element */
73             mCurrentPackage = mApiList.getOrCreatePackage(
74                     attributes.getValue("name"));
75         } else if (qName.equals("class") || qName.equals("interface")) {
76             /* get class, gather fields/methods and interfaces */
77             mCurrentClass = mCurrentPackage.getOrCreateClass(
78                     attributes.getValue("name"),
79                     attributes.getValue("extends"),
80                     attributes.getValue("static"));
81         } else if (qName.equals("implements")) {
82             /* add name of interface to current class */
83             mCurrentClass.addInterface(attributes.getValue("name"));
84         } else if (qName.equals("method")) {
85             /* hold object while we gather parameters */
86             mCurrentMethod = new MethodInfo(attributes.getValue("name"),
87                 attributes.getValue("return"));
88         } else if (qName.equals("constructor")) {
89             /* like "method", but has no name or return type */
90             mCurrentMethod = new MethodInfo("<init>", "void");
91 
92             /*
93              * If this is a non-static inner class, we want to add the
94              * "hidden" outer class parameter as the first parameter.
95              * We can tell if it's an inner class because the class name
96              * will include a '$' (it has been normalized already).
97              */
98             String staticClass = mCurrentClass.getStatic();
99             if (staticClass == null) {
100                 /*
101                  * We're parsing an APK file, which means we can't know
102                  * if the class we're referencing is static or not.  We
103                  * also already have the "secret" first parameter
104                  * represented in the method parameter list, so we don't
105                  * need to insert it here.
106                  */
107             } else if ("false".equals(staticClass)) {
108                 String className = mCurrentClass.getName();
109                 int dollarIndex = className.indexOf('$');
110                 if (dollarIndex >= 0) {
111                     String outerClass = className.substring(0, dollarIndex);
112                     //System.out.println("--- inserting " +
113                     //    mCurrentPackage.getName() + "." + outerClass +
114                     //    " into constructor for " + className);
115                     mCurrentMethod.addParameter(mCurrentPackage.getName() +
116                         "." + outerClass);
117                 }
118             }
119         } else if (qName.equals("field")) {
120             /* add to current class */
121             FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
122                     attributes.getValue("type"));
123             mCurrentClass.addField(fInfo);
124         } else if (qName.equals("parameter")) {
125             /* add to current method */
126             mCurrentMethod.addParameter(attributes.getValue("type"));
127         }
128     }
129 
130     /**
131      * Processes end tags.  Generally these add the under-construction
132      * item to the appropriate container.
133      */
134     @Override
endElement(String uri, String localName, String qName)135     public void endElement(String uri, String localName, String qName) {
136         if (qName.equals("method") || qName.equals("constructor")) {
137             /* add method to class */
138             mCurrentClass.addMethod(mCurrentMethod);
139             mCurrentMethod = null;
140         } else if (qName.equals("class") || qName.equals("interface")) {
141             mCurrentClass = null;
142         } else if (qName.equals("package")) {
143             mCurrentPackage = null;
144         }
145     }
146 }
147 
148