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 }