1/*
2 * Copyright (C) 2008 Google Inc.
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
17package com.android.hit;
18
19import java.util.ArrayList;
20import java.util.Collection;
21import java.util.HashSet;
22import java.util.Iterator;
23import java.util.Map;
24import java.util.Set;
25import java.util.TreeMap;
26import java.util.TreeSet;
27
28public class Queries {
29    /*
30     * NOTES:  Here's a list of the queries that can be done in hat and
31     * how you'd perform a similar query here in hit:
32     *
33     * hat                      hit
34     * ------------------------------------------------------------------------
35     * allClasses               classes
36     * allClassesWithPlatform   allClasses
37     * class                    findClass
38     * instances                instancesOf
39     * allInstances             allInstancesOf
40     * object                   findObject
41     * showRoots                getRoots
42     * newInstances             newInstances
43     *
44     * reachableFrom            make a call to findObject to get the target
45     *                          parent object, this will give you an Instance.
46     *                          Then call visit(Set, Filter) on that to have
47     *                          it build the set of objects in its subgraph.
48     *
49     * rootsTo                  make a call to findObject on the leaf node
50     *                          in question, this will give you an Instance.
51     *                          Instances have an ArrayList of all of the
52     *                          parent objects that refer to it.  You can
53     *                          follow those parent links until you hit an
54     *                          object whose parent is null or a ThreadObj.
55     *                          You've not successfully traced the paths to
56     *                          the roots.
57     */
58
59    private static final String DEFAULT_PACKAGE = "<default>";
60
61    /*
62     * Produce a collection of all classes, broken down by package.
63     * The keys of the resultant map iterate in sorted package order.
64     * The values of the map are the classes defined in each package.
65     */
66    public static Map<String, Set<ClassObj>> allClasses(State state) {
67        return classes(state, null);
68    }
69
70    public static Map<String, Set<ClassObj>> classes(State state,
71            String[] excludedPrefixes) {
72        TreeMap<String, Set<ClassObj>> result =
73        new TreeMap<String, Set<ClassObj>>();
74
75        Set<ClassObj> classes = new TreeSet<ClassObj>();
76
77        //  Build a set of all classes across all heaps
78        for (Heap heap: state.mHeaps.values()) {
79            classes.addAll(heap.mClassesById.values());
80        }
81
82        //  Filter it if needed
83        if (excludedPrefixes != null) {
84            final int N = excludedPrefixes.length;
85            Iterator<ClassObj> iter = classes.iterator();
86
87            while (iter.hasNext()) {
88                ClassObj theClass = iter.next();
89                String classPath = theClass.toString();
90
91                for (int i = 0; i < N; i++) {
92                    if (classPath.startsWith(excludedPrefixes[i])) {
93                        iter.remove();
94                        break;
95                    }
96                }
97            }
98        }
99
100        //  Now that we have a final list of classes, group them by package
101        for (ClassObj theClass: classes) {
102            String packageName = DEFAULT_PACKAGE;
103            int lastDot = theClass.mClassName.lastIndexOf('.');
104
105            if (lastDot != -1) {
106                packageName = theClass.mClassName.substring(0, lastDot);
107            }
108
109            Set<ClassObj> classSet = result.get(packageName);
110
111            if (classSet == null) {
112                classSet = new TreeSet<ClassObj>();
113                result.put(packageName, classSet);
114            }
115
116            classSet.add(theClass);
117        }
118
119        return result;
120    }
121
122    /*
123     * It's sorta sad that this is a pass-through call, but it seems like
124     * having all of the hat-like query methods in one place is a good thing
125     * even if there is duplication of effort.
126     */
127    public static ClassObj findClass(State state, String name) {
128        return state.findClass(name);
129    }
130
131    /*
132     * Return an array of instances of the given class.  This does not include
133     * instances of subclasses.
134     */
135     public static Instance[] instancesOf(State state, String baseClassName) {
136         ClassObj theClass = state.findClass(baseClassName);
137
138         if (theClass == null) {
139             throw new IllegalArgumentException("Class not found: "
140                + baseClassName);
141         }
142
143         Instance[] instances = new Instance[theClass.mInstances.size()];
144
145         return theClass.mInstances.toArray(instances);
146     }
147
148    /*
149     * Return an array of instances of the given class.  This includes
150     * instances of subclasses.
151     */
152    public static Instance[] allInstancesOf(State state, String baseClassName) {
153        ClassObj theClass = state.findClass(baseClassName);
154
155        if (theClass == null) {
156            throw new IllegalArgumentException("Class not found: "
157                + baseClassName);
158        }
159
160        ArrayList<ClassObj> classList = new ArrayList<ClassObj>();
161
162        classList.add(theClass);
163        classList.addAll(traverseSubclasses(theClass));
164
165        ArrayList<Instance> instanceList = new ArrayList<Instance>();
166
167        for (ClassObj someClass: classList) {
168            instanceList.addAll(someClass.mInstances);
169        }
170
171        Instance[] result = new Instance[instanceList.size()];
172
173        instanceList.toArray(result);
174
175        return result;
176    }
177
178    private static ArrayList<ClassObj> traverseSubclasses(ClassObj base) {
179        ArrayList<ClassObj> result = new ArrayList<ClassObj>();
180
181        for (ClassObj subclass: base.mSubclasses) {
182            result.add(subclass);
183            result.addAll(traverseSubclasses(subclass));
184        }
185
186        return result;
187    }
188
189    /*
190     * Find a reference to an object based on its id.  The id should be
191     * in hexadecimal.
192     */
193    public static Instance findObject(State state, String id) {
194        long id2 = Long.parseLong(id, 16);
195
196        return state.findReference(id2);
197    }
198
199    public static Collection<RootObj> getRoots(State state) {
200        HashSet<RootObj> result = new HashSet<RootObj>();
201
202        for (Heap heap: state.mHeaps.values()) {
203            result.addAll(heap.mRoots);
204        }
205
206        return result;
207    }
208
209    public static final Instance[] newInstances(State older, State newer) {
210        ArrayList<Instance> resultList = new ArrayList<Instance>();
211
212        for (Heap newHeap: newer.mHeaps.values()) {
213            Heap oldHeap = older.getHeap(newHeap.mName);
214
215            if (oldHeap == null) {
216                continue;
217            }
218
219            for (Instance instance: newHeap.mInstances.values()) {
220                Instance oldInstance = oldHeap.getInstance(instance.mId);
221
222                /*
223                 * If this instance wasn't in the old heap, or was there,
224                 * but that ID was for an obj of a different type, then we have
225                 * a newly allocated object and we should report it in the
226                 * results.
227                 */
228                if ((oldInstance == null)
229                        || (instance.mClassId != oldInstance.mClassId)) {
230                    resultList.add(instance);
231                }
232            }
233        }
234
235        Instance[] resultArray = new Instance[resultList.size()];
236
237        return resultList.toArray(resultArray);
238    }
239}
240