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