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 
17 package com.android.dx.rop.type;
18 
19 import 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  */
26 public 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      */
intern(String descriptor)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      */
makeParameterArray(String descriptor)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      */
intern(String descriptor, Type definer, boolean isStatic, boolean isInit)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      */
internInts(Type returnType, int count)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      */
Prototype(String descriptor, Type returnType, StdTypeList parameterTypes)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
equals(Object other)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
hashCode()255     public int hashCode() {
256         return descriptor.hashCode();
257     }
258 
259     /** {@inheritDoc} */
compareTo(Prototype other)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
toString()303     public String toString() {
304         return descriptor;
305     }
306 
307     /**
308      * Gets the descriptor string.
309      *
310      * @return {@code non-null;} the descriptor
311      */
getDescriptor()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      */
getReturnType()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      */
getParameterTypes()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      */
getParameterFrameTypes()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      */
withFirstParameter(Type param)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      */
putIntern(Prototype desc)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