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