Prototype.java revision 579d7739c53a2707ad711a2d2cae46d7d782f061
1/*
2 * Copyright (C) 2007 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
17package com.android.dx.rop.type;
18
19import java.util.HashMap;
20
21/**
22 * Representation of a method descriptor. Instances of this class are
23 * generally interned and may be usefully compared with each other
24 * using {@code ==}.
25 */
26public final class Prototype implements Comparable<Prototype> {
27    /** {@code non-null;} intern table mapping string descriptors to instances */
28    private static final HashMap<String, Prototype> internTable =
29        new HashMap<String, Prototype>(500);
30
31    /** {@code non-null;} method descriptor */
32    private final String descriptor;
33
34    /** {@code non-null;} return type */
35    private final Type returnType;
36
37    /** {@code non-null;} list of parameter types */
38    private final StdTypeList parameterTypes;
39
40    /** {@code null-ok;} list of parameter frame types, if calculated */
41    private StdTypeList parameterFrameTypes;
42
43    /**
44     * Returns the unique instance corresponding to the
45     * given method descriptor. See vmspec-2 sec4.3.3 for details on the
46     * field descriptor syntax.
47     *
48     * @param descriptor {@code non-null;} the descriptor
49     * @return {@code non-null;} the corresponding instance
50     * @throws IllegalArgumentException thrown if the descriptor has
51     * invalid syntax
52     */
53    public static Prototype intern(String descriptor) {
54        if (descriptor == null) {
55            throw new NullPointerException("descriptor == null");
56        }
57
58        Prototype result;
59        synchronized (internTable) {
60            result = internTable.get(descriptor);
61        }
62        if (result != null) {
63            return result;
64        }
65
66        Type[] params = makeParameterArray(descriptor);
67        int paramCount = 0;
68        int at = 1;
69
70        for (;;) {
71            int startAt = at;
72            char c = descriptor.charAt(at);
73            if (c == ')') {
74                at++;
75                break;
76            }
77
78            // Skip array markers.
79            while (c == '[') {
80                at++;
81                c = descriptor.charAt(at);
82            }
83
84            if (c == 'L') {
85                // It looks like the start of a class name; find the end.
86                int endAt = descriptor.indexOf(';', at);
87                if (endAt == -1) {
88                    throw new IllegalArgumentException("bad descriptor");
89                }
90                at = endAt + 1;
91            } else {
92                at++;
93            }
94
95            params[paramCount] =
96                Type.intern(descriptor.substring(startAt, at));
97            paramCount++;
98        }
99
100        Type returnType = Type.internReturnType(descriptor.substring(at));
101        StdTypeList parameterTypes = new StdTypeList(paramCount);
102
103        for (int i = 0; i < paramCount; i++) {
104            parameterTypes.set(i, params[i]);
105        }
106
107        result = new Prototype(descriptor, returnType, parameterTypes);
108        return putIntern(result);
109    }
110
111    /**
112     * Helper for {@link #intern} which returns an empty array to
113     * populate with parsed parameter types, and which also ensures
114     * that there is a '(' at the start of the descriptor and a
115     * single ')' somewhere before the end.
116     *
117     * @param descriptor {@code non-null;} the descriptor string
118     * @return {@code non-null;} array large enough to hold all parsed parameter
119     * types, but which is likely actually larger than needed
120     */
121    private static Type[] makeParameterArray(String descriptor) {
122        int length = descriptor.length();
123
124        if (descriptor.charAt(0) != '(') {
125            throw new IllegalArgumentException("bad descriptor");
126        }
127
128        /*
129         * This is a cheesy way to establish an upper bound on the
130         * number of parameters: Just count capital letters.
131         */
132        int closeAt = 0;
133        int maxParams = 0;
134        for (int i = 1; i < length; i++) {
135            char c = descriptor.charAt(i);
136            if (c == ')') {
137                closeAt = i;
138                break;
139            }
140            if ((c >= 'A') && (c <= 'Z')) {
141                maxParams++;
142            }
143        }
144
145        if ((closeAt == 0) || (closeAt == (length - 1))) {
146            throw new IllegalArgumentException("bad descriptor");
147        }
148
149        if (descriptor.indexOf(')', closeAt + 1) != -1) {
150            throw new IllegalArgumentException("bad descriptor");
151        }
152
153        return new Type[maxParams];
154    }
155
156    /**
157     * Interns an instance, adding to the descriptor as necessary based
158     * on the given definer, name, and flags. For example, an init
159     * method has an uninitialized object of type {@code definer}
160     * as its first argument.
161     *
162     * @param descriptor {@code non-null;} the descriptor string
163     * @param definer {@code non-null;} class the method is defined on
164     * @param isStatic whether this is a static method
165     * @param isInit whether this is an init method
166     * @return {@code non-null;} the interned instance
167     */
168    public static Prototype intern(String descriptor, Type definer,
169            boolean isStatic, boolean isInit) {
170        Prototype base = intern(descriptor);
171
172        if (isStatic) {
173            return base;
174        }
175
176        if (isInit) {
177            definer = definer.asUninitialized(Integer.MAX_VALUE);
178        }
179
180        return base.withFirstParameter(definer);
181    }
182
183    /**
184     * Interns an instance which consists of the given number of
185     * {@code int}s along with the given return type
186     *
187     * @param returnType {@code non-null;} the return type
188     * @param count {@code > 0;} the number of elements in the prototype
189     * @return {@code non-null;} the interned instance
190     */
191    public static Prototype internInts(Type returnType, int count) {
192        // Make the descriptor...
193
194        StringBuffer sb = new StringBuffer(100);
195
196        sb.append('(');
197
198        for (int i = 0; i < count; i++) {
199            sb.append('I');
200        }
201
202        sb.append(')');
203        sb.append(returnType.getDescriptor());
204
205        // ...and intern it.
206        return intern(sb.toString());
207    }
208
209    /**
210     * Constructs an instance. This is a private constructor; use one
211     * of the public static methods to get instances.
212     *
213     * @param descriptor {@code non-null;} the descriptor string
214     */
215    private Prototype(String descriptor, Type returnType,
216            StdTypeList parameterTypes) {
217        if (descriptor == null) {
218            throw new NullPointerException("descriptor == null");
219        }
220
221        if (returnType == null) {
222            throw new NullPointerException("returnType == null");
223        }
224
225        if (parameterTypes == null) {
226            throw new NullPointerException("parameterTypes == null");
227        }
228
229        this.descriptor = descriptor;
230        this.returnType = returnType;
231        this.parameterTypes = parameterTypes;
232        this.parameterFrameTypes = null;
233    }
234
235    /** {@inheritDoc} */
236    @Override
237    public boolean equals(Object other) {
238        if (this == other) {
239            /*
240             * Since externally-visible instances are interned, this
241             * check helps weed out some easy cases.
242             */
243            return true;
244        }
245
246        if (!(other instanceof Prototype)) {
247            return false;
248        }
249
250        return descriptor.equals(((Prototype) other).descriptor);
251    }
252
253    /** {@inheritDoc} */
254    @Override
255    public int hashCode() {
256        return descriptor.hashCode();
257    }
258
259    /** {@inheritDoc} */
260    public int compareTo(Prototype other) {
261        if (this == other) {
262            return 0;
263        }
264
265        /*
266         * The return type is the major order, and then args in order,
267         * and then the shorter list comes first (similar to string
268         * sorting).
269         */
270
271        int result = returnType.compareTo(other.returnType);
272
273        if (result != 0) {
274            return result;
275        }
276
277        int thisSize = parameterTypes.size();
278        int otherSize = other.parameterTypes.size();
279        int size = Math.min(thisSize, otherSize);
280
281        for (int i = 0; i < size; i++) {
282            Type thisType = parameterTypes.get(i);
283            Type otherType = other.parameterTypes.get(i);
284
285            result = thisType.compareTo(otherType);
286
287            if (result != 0) {
288                return result;
289            }
290        }
291
292        if (thisSize < otherSize) {
293            return -1;
294        } else if (thisSize > otherSize) {
295            return 1;
296        } else {
297            return 0;
298        }
299    }
300
301    /** {@inheritDoc} */
302    @Override
303    public String toString() {
304        return descriptor;
305    }
306
307    /**
308     * Gets the descriptor string.
309     *
310     * @return {@code non-null;} the descriptor
311     */
312    public String getDescriptor() {
313        return descriptor;
314    }
315
316    /**
317     * Gets the return type.
318     *
319     * @return {@code non-null;} the return type
320     */
321    public Type getReturnType() {
322        return returnType;
323    }
324
325    /**
326     * Gets the list of parameter types.
327     *
328     * @return {@code non-null;} the list of parameter types
329     */
330    public StdTypeList getParameterTypes() {
331        return parameterTypes;
332    }
333
334    /**
335     * Gets the list of frame types corresponding to the list of parameter
336     * types. The difference between the two lists (if any) is that all
337     * "intlike" types (see {@link Type#isIntlike}) are replaced by
338     * {@link Type#INT}.
339     *
340     * @return {@code non-null;} the list of parameter frame types
341     */
342    public StdTypeList getParameterFrameTypes() {
343        if (parameterFrameTypes == null) {
344            int sz = parameterTypes.size();
345            StdTypeList list = new StdTypeList(sz);
346            boolean any = false;
347            for (int i = 0; i < sz; i++) {
348                Type one = parameterTypes.get(i);
349                if (one.isIntlike()) {
350                    any = true;
351                    one = Type.INT;
352                }
353                list.set(i, one);
354            }
355            parameterFrameTypes = any ? list : parameterTypes;
356        }
357
358        return parameterFrameTypes;
359    }
360
361    /**
362     * Returns a new interned instance, which is the same as this instance,
363     * except that it has an additional parameter prepended to the original's
364     * argument list.
365     *
366     * @param param {@code non-null;} the new first parameter
367     * @return {@code non-null;} an appropriately-constructed instance
368     */
369    public Prototype withFirstParameter(Type param) {
370        String newDesc = "(" + param.getDescriptor() + descriptor.substring(1);
371        StdTypeList newParams = parameterTypes.withFirst(param);
372
373        newParams.setImmutable();
374
375        Prototype result =
376            new Prototype(newDesc, returnType, newParams);
377
378        return putIntern(result);
379    }
380
381    /**
382     * Puts the given instance in the intern table if it's not already
383     * there. If a conflicting value is already in the table, then leave it.
384     * Return the interned value.
385     *
386     * @param desc {@code non-null;} instance to make interned
387     * @return {@code non-null;} the actual interned object
388     */
389    private static Prototype putIntern(Prototype desc) {
390        synchronized (internTable) {
391            String descriptor = desc.getDescriptor();
392            Prototype already = internTable.get(descriptor);
393            if (already != null) {
394                return already;
395            }
396            internTable.put(descriptor, desc);
397            return desc;
398        }
399    }
400}
401