1 /*
2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package java.lang;
26 
27 import jdk.internal.reflect.ReflectionFactory;
28 
29 import java.lang.reflect.Method;
30 import java.lang.reflect.Modifier;
31 import java.security.AccessController;
32 import java.util.Arrays;
33 import java.util.LinkedHashMap;
34 import java.util.Map;
35 
36 /**
37  * A collection of most specific public methods. Methods are added to it using
38  * {@link #merge(Method)} method. Only the most specific methods for a
39  * particular signature are kept.
40  */
41 final class PublicMethods {
42 
43     /**
44      * a map of (method name, parameter types) -> linked list of Method(s)
45      */
46     private final Map<Key, MethodList> map = new LinkedHashMap<>();
47 
48     /**
49      * keeps track of the number of collected methods
50      */
51     private int methodCount;
52 
53     /**
54      * Merges new method with existing methods. New method is either
55      * ignored (if a more specific method with same signature exists) or added
56      * to the collection. When it is added to the collection, it may replace one
57      * or more existing methods with same signature if they are less specific
58      * than added method.
59      * See comments in code...
60      */
merge(Method method)61     void merge(Method method) {
62         Key key = new Key(method);
63         MethodList existing = map.get(key);
64         int xLen = existing == null ? 0 : existing.length();
65         MethodList merged = MethodList.merge(existing, method);
66         methodCount += merged.length() - xLen;
67         // replace if head of list changed
68         if (merged != existing) {
69             map.put(key, merged);
70         }
71     }
72 
73     /**
74      * Dumps methods to array.
75      */
toArray()76     Method[] toArray() {
77         Method[] array = new Method[methodCount];
78         int i = 0;
79         for (MethodList ml : map.values()) {
80             for (; ml != null; ml = ml.next) {
81                 array[i++] = ml.method;
82             }
83         }
84         return array;
85     }
86 
87     /**
88      * Method (name, parameter types) tuple.
89      */
90     private static final class Key {
91         private static final ReflectionFactory reflectionFactory =
92             AccessController.doPrivileged(
93                 new ReflectionFactory.GetReflectionFactoryAction());
94 
95         private final String name; // must be interned (as from Method.getName())
96         private final Class<?>[] ptypes;
97 
Key(Method method)98         Key(Method method) {
99             name = method.getName();
100             ptypes = reflectionFactory.getExecutableSharedParameterTypes(method);
101         }
102 
matches(Method method, String name, Class<?>[] ptypes)103         static boolean matches(Method method,
104                                String name, // may not be interned
105                                Class<?>[] ptypes) {
106             return method.getName().equals(name) &&
107                    Arrays.equals(
108                        reflectionFactory.getExecutableSharedParameterTypes(method),
109                        ptypes
110                    );
111         }
112 
113         @Override
equals(Object o)114         public boolean equals(Object o) {
115             if (this == o) return true;
116             if (!(o instanceof Key)) return false;
117             Key that = (Key) o;
118             //noinspection StringEquality (guaranteed interned String(s))
119             return name == that.name &&
120                    Arrays.equals(ptypes, that.ptypes);
121         }
122 
123         @Override
hashCode()124         public int hashCode() {
125             return System.identityHashCode(name) + // guaranteed interned String
126                    31 * Arrays.hashCode(ptypes);
127         }
128     }
129 
130     /**
131      * Node of a inked list containing Method(s) sharing the same
132      * (name, parameter types) tuple.
133      */
134     static final class MethodList {
135         Method method;
136         MethodList next;
137 
MethodList(Method method)138         private MethodList(Method method) {
139             this.method = method;
140         }
141 
142         /**
143          * @return the head of a linked list containing given {@code methods}
144          *         filtered by given method {@code name}, parameter types
145          *         {@code ptypes} and including or excluding static methods as
146          *         requested by {@code includeStatic} flag.
147          */
filter(Method[] methods, String name, Class<?>[] ptypes, boolean includeStatic)148         static MethodList filter(Method[] methods, String name,
149                                  Class<?>[] ptypes, boolean includeStatic) {
150             MethodList head = null, tail = null;
151             for (Method method : methods) {
152                 if ((includeStatic || !Modifier.isStatic(method.getModifiers())) &&
153                     Key.matches(method, name, ptypes)) {
154                     if (tail == null) {
155                         head = tail = new MethodList(method);
156                     } else {
157                         tail = tail.next = new MethodList(method);
158                     }
159                 }
160             }
161             return head;
162         }
163 
164         /**
165          * This method should only be called with the {@code head} (possibly null)
166          * of a list of Method(s) that share the same (method name, parameter types)
167          * and another {@code methodList} that also contains Method(s) with the
168          * same and equal (method name, parameter types) as the 1st list.
169          * It modifies the 1st list and returns the head of merged list
170          * containing only the most specific methods for each signature
171          * (i.e. return type). The returned head of the merged list may or
172          * may not be the same as the {@code head} of the given list.
173          * The given {@code methodList} is not modified.
174          */
merge(MethodList head, MethodList methodList)175         static MethodList merge(MethodList head, MethodList methodList) {
176             for (MethodList ml = methodList; ml != null; ml = ml.next) {
177                 head = merge(head, ml.method);
178             }
179             return head;
180         }
181 
merge(MethodList head, Method method)182         private static MethodList merge(MethodList head, Method method) {
183             Class<?> dclass = method.getDeclaringClass();
184             Class<?> rtype = method.getReturnType();
185             MethodList prev = null;
186             for (MethodList l = head; l != null; l = l.next) {
187                 // eXisting method
188                 Method xmethod = l.method;
189                 // only merge methods with same signature:
190                 // (return type, name, parameter types) tuple
191                 // as we only keep methods with same (name, parameter types)
192                 // tuple together in one list, we only need to check return type
193                 if (rtype == xmethod.getReturnType()) {
194                     Class<?> xdclass = xmethod.getDeclaringClass();
195                     if (dclass.isInterface() == xdclass.isInterface()) {
196                         // both methods are declared by interfaces
197                         // or both by classes
198                         if (dclass.isAssignableFrom(xdclass)) {
199                             // existing method is the same or overrides
200                             // new method - ignore new method
201                             return head;
202                         }
203                         if (xdclass.isAssignableFrom(dclass)) {
204                             // new method overrides existing
205                             // method - knock out existing method
206                             if (prev != null) {
207                                 prev.next = l.next;
208                             } else {
209                                 head = l.next;
210                             }
211                             // keep iterating
212                         } else {
213                             // unrelated (should only happen for interfaces)
214                             prev = l;
215                             // keep iterating
216                         }
217                     } else if (dclass.isInterface()) {
218                         // new method is declared by interface while
219                         // existing method is declared by class -
220                         // ignore new method
221                         return head;
222                     } else /* xdclass.isInterface() */ {
223                         // new method is declared by class while
224                         // existing method is declared by interface -
225                         // knock out existing method
226                         if (prev != null) {
227                             prev.next = l.next;
228                         } else {
229                             head = l.next;
230                         }
231                         // keep iterating
232                     }
233                 } else {
234                     // distinct signatures
235                     prev = l;
236                     // keep iterating
237                 }
238             }
239             // append new method to the list
240             if (prev == null) {
241                 head = new MethodList(method);
242             } else {
243                 prev.next = new MethodList(method);
244             }
245             return head;
246         }
247 
length()248         private int length() {
249             int len = 1;
250             for (MethodList ml = next; ml != null; ml = ml.next) {
251                 len++;
252             }
253             return len;
254         }
255 
256         /**
257          * @return 1st method in list with most specific return type
258          */
getMostSpecific()259         Method getMostSpecific() {
260             Method m = method;
261             Class<?> rt = m.getReturnType();
262             for (MethodList ml = next; ml != null; ml = ml.next) {
263                 Method m2 = ml.method;
264                 Class<?> rt2 = m2.getReturnType();
265                 if (rt2 != rt && rt.isAssignableFrom(rt2)) {
266                     // found more specific return type
267                     m = m2;
268                     rt = rt2;
269                 }
270             }
271             return m;
272         }
273     }
274 }
275