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