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 */
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 */
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
98 Key(Method method) {
99 name = method.getName();
100 ptypes = reflectionFactory.getExecutableSharedParameterTypes(method);
101 }
102
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
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
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
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 */
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 */
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
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
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 */
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