--- old/src/java.base/share/classes/java/lang/Class.java 2014-11-30 18:51:40.478575798 +0100 +++ new/src/java.base/share/classes/java/lang/Class.java 2014-11-30 18:51:40.391577334 +0100 @@ -45,7 +45,6 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -411,7 +410,9 @@ } try { Class[] empty = {}; - final Constructor c = getConstructor0(empty, Member.DECLARED); + // must copy constructor since we are modifying it + final Constructor c = getReflectionFactory().copyConstructor( + getConstructor0(empty, Member.DECLARED)); // Disable accessibility checks on the constructor // since we have to do the security check here anyway // (the stack depth is wrong for the Constructor's @@ -842,6 +843,10 @@ * @return an array of interfaces directly implemented by this class */ public Class[] getInterfaces() { + return getInterfaces(true); + } + + private Class[] getInterfaces(boolean cloneArray) { ReflectionData rd = reflectionData(); if (rd == null) { // no cloning required @@ -852,8 +857,8 @@ interfaces = getInterfaces0(); rd.interfaces = interfaces; } - // defensively copy before handing over to user code - return interfaces.clone(); + // defensively copy cached array before handing over to user code + return cloneArray ? interfaces.clone() : interfaces; } } @@ -1710,7 +1715,7 @@ if (field == null) { throw new NoSuchFieldException(name); } - return field; + return getReflectionFactory().copyField(field); } @@ -1793,7 +1798,7 @@ if (method == null) { throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes)); } - return method; + return getReflectionFactory().copyMethod(method); } @@ -1830,7 +1835,8 @@ public Constructor getConstructor(Class... parameterTypes) throws NoSuchMethodException, SecurityException { checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); - return getConstructor0(parameterTypes, Member.PUBLIC); + return getReflectionFactory().copyConstructor( + getConstructor0(parameterTypes, Member.PUBLIC)); } @@ -2077,7 +2083,7 @@ if (field == null) { throw new NoSuchFieldException(name); } - return field; + return getReflectionFactory().copyField(field); } @@ -2137,7 +2143,7 @@ if (method == null) { throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes)); } - return method; + return getReflectionFactory().copyMethod(method); } @@ -2183,7 +2189,8 @@ public Constructor getDeclaredConstructor(Class... parameterTypes) throws NoSuchMethodException, SecurityException { checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true); - return getConstructor0(parameterTypes, Member.DECLARED); + return getReflectionFactory().copyConstructor( + getConstructor0(parameterTypes, Member.DECLARED)); } /** @@ -2623,7 +2630,7 @@ addAll(fields, tmp); // Direct superinterfaces, recursively - for (Class c : getInterfaces()) { + for (Class c : getInterfaces(false)) { if (!traversedInterfaces.contains(c)) { traversedInterfaces.add(c); addAll(fields, c.privateGetPublicFields(traversedInterfaces)); @@ -2717,179 +2724,7 @@ return res; } - static class MethodArray { - // Don't add or remove methods except by add() or remove() calls. - private Method[] methods; - private int length; - private int defaults; - - MethodArray() { - this(20); - } - - MethodArray(int initialSize) { - if (initialSize < 2) - throw new IllegalArgumentException("Size should be 2 or more"); - - methods = new Method[initialSize]; - length = 0; - defaults = 0; - } - - boolean hasDefaults() { - return defaults != 0; - } - - void add(Method m) { - if (length == methods.length) { - methods = Arrays.copyOf(methods, 2 * methods.length); - } - methods[length++] = m; - - if (m != null && m.isDefault()) - defaults++; - } - - void addAll(Method[] ma) { - for (Method m : ma) { - add(m); - } - } - - void addAll(MethodArray ma) { - for (int i = 0; i < ma.length(); i++) { - add(ma.get(i)); - } - } - - void addIfNotPresent(Method newMethod) { - for (int i = 0; i < length; i++) { - Method m = methods[i]; - if (m == newMethod || (m != null && m.equals(newMethod))) { - return; - } - } - add(newMethod); - } - - void addAllIfNotPresent(MethodArray newMethods) { - for (int i = 0; i < newMethods.length(); i++) { - Method m = newMethods.get(i); - if (m != null) { - addIfNotPresent(m); - } - } - } - - /* Add Methods declared in an interface to this MethodArray. - * Static methods declared in interfaces are not inherited. - */ - void addInterfaceMethods(Method[] methods) { - for (Method candidate : methods) { - if (!Modifier.isStatic(candidate.getModifiers())) { - add(candidate); - } - } - } - - int length() { - return length; - } - - Method get(int i) { - return methods[i]; - } - - Method getFirst() { - for (Method m : methods) - if (m != null) - return m; - return null; - } - - void removeByNameAndDescriptor(Method toRemove) { - for (int i = 0; i < length; i++) { - Method m = methods[i]; - if (m != null && matchesNameAndDescriptor(m, toRemove)) { - remove(i); - } - } - } - - private void remove(int i) { - if (methods[i] != null && methods[i].isDefault()) - defaults--; - methods[i] = null; - } - - private boolean matchesNameAndDescriptor(Method m1, Method m2) { - return m1.getReturnType() == m2.getReturnType() && - m1.getName() == m2.getName() && // name is guaranteed to be interned - arrayContentsEq(m1.getParameterTypes(), - m2.getParameterTypes()); - } - - void compactAndTrim() { - int newPos = 0; - // Get rid of null slots - for (int pos = 0; pos < length; pos++) { - Method m = methods[pos]; - if (m != null) { - if (pos != newPos) { - methods[newPos] = m; - } - newPos++; - } - } - if (newPos != methods.length) { - methods = Arrays.copyOf(methods, newPos); - } - } - - /* Removes all Methods from this MethodArray that have a more specific - * default Method in this MethodArray. - * - * Users of MethodArray are responsible for pruning Methods that have - * a more specific concrete Method. - */ - void removeLessSpecifics() { - if (!hasDefaults()) - return; - - for (int i = 0; i < length; i++) { - Method m = get(i); - if (m == null || !m.isDefault()) - continue; - - for (int j = 0; j < length; j++) { - if (i == j) - continue; - - Method candidate = get(j); - if (candidate == null) - continue; - - if (!matchesNameAndDescriptor(m, candidate)) - continue; - - if (hasMoreSpecificClass(m, candidate)) - remove(j); - } - } - } - - Method[] getArray() { - return methods; - } - - // Returns true if m1 is more specific than m2 - static boolean hasMoreSpecificClass(Method m1, Method m2) { - Class m1Class = m1.getDeclaringClass(); - Class m2Class = m2.getDeclaringClass(); - return m1Class != m2Class && m2Class.isAssignableFrom(m1Class); - } - } - + private static final Method[] EMPTY_METHODS = new Method[0]; // Returns an array of "root" methods. These Method objects must NOT // be propagated to the outside world, but must instead be copied @@ -2903,52 +2738,59 @@ if (res != null) return res; } - // No cached value available; compute value recursively. - // Start by fetching public declared methods - MethodArray methods = new MethodArray(); - { - Method[] tmp = privateGetDeclaredMethods(true); - methods.addAll(tmp); - } - // Now recur over superclass and direct superinterfaces. - // Go over superinterfaces first so we can more easily filter - // out concrete implementations inherited from superclasses at - // the end. - MethodArray inheritedMethods = new MethodArray(); - for (Class i : getInterfaces()) { - inheritedMethods.addInterfaceMethods(i.privateGetPublicMethods()); - } - if (!isInterface()) { - Class c = getSuperclass(); - if (c != null) { - MethodArray supers = new MethodArray(); - supers.addAll(c.privateGetPublicMethods()); - // Filter out concrete implementations of any - // interface methods - for (int i = 0; i < supers.length(); i++) { - Method m = supers.get(i); - if (m != null && - !Modifier.isAbstract(m.getModifiers()) && - !m.isDefault()) { - inheritedMethods.removeByNameAndDescriptor(m); + Method[] declaredMethods = privateGetDeclaredMethods(true); + Class superclass = getSuperclass(); + Class[] interfaces = getInterfaces(false); + + // optimization: + // if we don't have a superclass (either we are j.l.Object or an interface) + // and don't have (super)interfaces either, then public methods consist + // of declared public methods + if (superclass == null && interfaces.length == 0) { + res = declaredMethods; + } else { + // we have to do some logic + Method[] superclassMethods = (superclass == null) + ? EMPTY_METHODS + : superclass.privateGetPublicMethods(); + Method[][] interfacesMethods = new Method[interfaces.length][]; + int interfacesMethodsCount = 0; + for (int i = 0; i < interfaces.length; i++) { + interfacesMethods[i] = interfaces[i].privateGetPublicMethods(); + interfacesMethodsCount += interfacesMethods[i].length; + } + + // ensure enough working capacity + // (MethodTable implementation may not support dynamic resizing) + MethodTable methodTable = MethodTable.newInstance( + declaredMethods.length + + superclassMethods.length + + interfacesMethodsCount + ); + + // declared methods first + for (Method m : declaredMethods) { + methodTable.add(m); + } + + // inherited methods from superclass + for (Method m : superclassMethods) { + methodTable.addUnlessDeclaredExists(m, this); + } + + // inherited methods from (super)interfaces + for (Method[] ms : interfacesMethods) { + for (Method m : ms) { + // interface static methods are not inherited + if (!Modifier.isStatic(m.getModifiers())) { + methodTable.consolidate(m, this); } } - // Insert superclass's inherited methods before - // superinterfaces' to satisfy getMethod's search - // order - supers.addAll(inheritedMethods); - inheritedMethods = supers; - } - } - // Filter out all local methods from inherited ones - for (int i = 0; i < methods.length(); i++) { - Method m = methods.get(i); - inheritedMethods.removeByNameAndDescriptor(m); - } - methods.addAllIfNotPresent(inheritedMethods); - methods.removeLessSpecifics(); - methods.compactAndTrim(); - res = methods.getArray(); + } + + res = methodTable.getMethods(); + } + if (rd != null) { rd.publicMethods = res; } @@ -2960,16 +2802,25 @@ // Helpers for fetchers of one field, method, or constructor // + /** + * This method does not copy returned 'root' Field object. It MUST be copied + * with ReflectionFactory before handed to any code outside java.lang.Class + * or modified. + */ private static Field searchFields(Field[] fields, String name) { String internedName = name.intern(); for (Field field : fields) { if (field.getName() == internedName) { - return getReflectionFactory().copyField(field); + return field; } } return null; } + /** + * This method returns 'root' Field object. It MUST be copied with ReflectionFactory + * before handed to any code outside java.lang.Class or modified. + */ private Field getField0(String name) throws NoSuchFieldException { // Note: the intent is that the search algorithm this routine // uses be equivalent to the ordering imposed by @@ -2984,7 +2835,7 @@ return res; } // Direct superinterfaces, recursively - Class[] interfaces = getInterfaces(); + Class[] interfaces = getInterfaces(false); for (Class c : interfaces) { if ((res = c.getField0(name)) != null) { return res; @@ -3002,6 +2853,11 @@ return null; } + /** + * This method does not copy returned 'root' Method object. It MUST be copied + * with ReflectionFactory before handed to any code outside java.lang.Class + * or modified. + */ private static Method searchMethods(Method[] methods, String name, Class[] parameterTypes) @@ -3016,24 +2872,14 @@ res = m; } - return (res == null ? res : getReflectionFactory().copyMethod(res)); + return res; } + /** + * This method returns 'root' Method object. It MUST be copied with ReflectionFactory + * before handed to any code outside java.lang.Class or modified. + */ private Method getMethod0(String name, Class[] parameterTypes, boolean includeStaticMethods) { - MethodArray interfaceCandidates = new MethodArray(2); - Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates); - if (res != null) - return res; - - // Not found on class or superclass directly - interfaceCandidates.removeLessSpecifics(); - return interfaceCandidates.getFirst(); // may be null - } - - private Method privateGetMethodRecursive(String name, - Class[] parameterTypes, - boolean includeStaticMethods, - MethodArray allInterfaceCandidates) { // Note: the intent is that the search algorithm this routine // uses be equivalent to the ordering imposed by // privateGetPublicMethods(). It fetches only the declared @@ -3041,40 +2887,45 @@ // number of Method objects which have to be created for the // common case where the method being requested is declared in // the class which is being queried. - // - // Due to default methods, unless a method is found on a superclass, - // methods declared in any superinterface needs to be considered. - // Collect all candidates declared in superinterfaces in {@code - // allInterfaceCandidates} and select the most specific if no match on - // a superclass is found. - - // Must _not_ return root methods Method res; - // Search declared public methods + // 1st search declared public methods if ((res = searchMethods(privateGetDeclaredMethods(true), name, - parameterTypes)) != null) { - if (includeStaticMethods || !Modifier.isStatic(res.getModifiers())) - return res; + parameterTypes)) != null && + (includeStaticMethods || + !Modifier.isStatic(res.getModifiers()))) { + return res; } - // Search superclass's methods - if (!isInterface()) { - Class c = getSuperclass(); - if (c != null) { - if ((res = c.getMethod0(name, parameterTypes, true)) != null) { - return res; - } + + // 2nd we try the superclass. + Class superclass = getSuperclass(); + if (superclass != null && + (res = superclass.getMethod0(name, parameterTypes, includeStaticMethods)) != null) { + return res; + } + + // last we check (super) interfaces + Class[] interfaces = getInterfaces(false); + if (interfaces.length == 0) { + return null; + } + // have to construct a MethodTable to consolidate + // public methods from (super)interfaces + MethodTable methodTable = MethodTable.newInstance(interfaces.length); + for (Class c : interfaces) { + if ((res = c.getMethod0(name, parameterTypes, false)) != null) { + methodTable.consolidate(res, this); } } - // Search superinterfaces' methods - Class[] interfaces = getInterfaces(); - for (Class c : interfaces) - if ((res = c.getMethod0(name, parameterTypes, false)) != null) - allInterfaceCandidates.add(res); - // Not found - return null; + + // return the first method with the most specific return type + return methodTable.getFirstMethodWithMostSpecificReturnType(); } + /** + * This method returns 'root' Constructor object. It MUST be copied with ReflectionFactory + * before handed to any code outside java.lang.Class or modified. + */ private Constructor getConstructor0(Class[] parameterTypes, int which) throws NoSuchMethodException { @@ -3082,7 +2933,7 @@ for (Constructor constructor : constructors) { if (arrayContentsEq(parameterTypes, constructor.getParameterTypes())) { - return getReflectionFactory().copyConstructor(constructor); + return constructor; } } throw new NoSuchMethodException(getName() + "." + argumentTypesToString(parameterTypes)); --- old/src/java.base/share/classes/java/lang/reflect/Method.java 2014-11-30 18:51:40.787570344 +0100 +++ new/src/java.base/share/classes/java/lang/reflect/Method.java 2014-11-30 18:51:40.671572392 +0100 @@ -37,6 +37,7 @@ import java.lang.annotation.Annotation; import java.lang.annotation.AnnotationFormatError; import java.nio.ByteBuffer; +import java.util.Arrays; /** * A {@code Method} provides information about, and access to, a single method @@ -178,6 +179,27 @@ } /** + * Used by java.lang.MethodTable to optimize comparing/locating + * method signatures. + */ + boolean parameterTypesEquals(Method other) { + int len = parameterTypes.length; + if (len != other.parameterTypes.length) { + return false; + } + for (int i = 0; i < len; i++) { + if (parameterTypes[i] != other.parameterTypes[i]) { + return false; + } + } + return true; + } + + int parameterTypesHashCode() { + return Arrays.hashCode(parameterTypes); + } + + /** * {@inheritDoc} */ @Override --- old/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java 2014-11-30 18:51:41.063565473 +0100 +++ new/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java 2014-11-30 18:51:40.977566991 +0100 @@ -147,4 +147,15 @@ public Constructor copyConstructor(Constructor arg) { return arg.copy(); } + + // + // Comparing / Hashing Method parameter types + // + public boolean methodParameterTypesEquals(Method method1, Method method2) { + return method1.parameterTypesEquals(method2); + } + + public int methodParameterTypesHashCode(Method method) { + return method.parameterTypesHashCode(); + } } --- old/src/java.base/share/classes/sun/reflect/LangReflectAccess.java 2014-11-30 18:51:41.336560654 +0100 +++ new/src/java.base/share/classes/sun/reflect/LangReflectAccess.java 2014-11-30 18:51:41.220562702 +0100 @@ -109,4 +109,14 @@ /** Makes a "child" copy of a Constructor */ public Constructor copyConstructor(Constructor arg); + + // + // Comparing / Hashing Method parameter types + // + + /** Compares parameter types of two Methods */ + public boolean methodParameterTypesEquals(Method method1, Method method2); + + /** Returns hashCode for parameter types of a Method */ + public int methodParameterTypesHashCode(Method method); } --- old/src/java.base/share/classes/sun/reflect/ReflectionFactory.java 2014-11-30 18:51:41.635555377 +0100 +++ new/src/java.base/share/classes/sun/reflect/ReflectionFactory.java 2014-11-30 18:51:41.534557159 +0100 @@ -322,6 +322,16 @@ return langReflectAccess().getExecutableTypeAnnotationBytes(ex); } + /** Compares parameter types of two Methods */ + public boolean methodParameterTypesEquals(Method method1, Method method2) { + return langReflectAccess().methodParameterTypesEquals(method1, method2); + } + + /** Returns hashCode for parameter types of a Method */ + public int methodParameterTypesHashCode(Method method) { + return langReflectAccess().methodParameterTypesHashCode(method); + } + //-------------------------------------------------------------------------- // // Routines used by serialization --- old/test/java/lang/Class/getMethods/StarInheritance.java 2014-11-30 18:51:41.922550311 +0100 +++ new/test/java/lang/Class/getMethods/StarInheritance.java 2014-11-30 18:51:41.819552129 +0100 @@ -30,6 +30,9 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; // D.m interface A1 extends B1, C1 {} @@ -55,21 +58,41 @@ interface C4 extends D4 {} interface D4 { void m(); } +// D.m +abstract class A_1 extends B_1 implements C1 {} +abstract class B_1 implements D1 {} + +// A_.m +abstract class A_2 extends B_2 implements C2 { public abstract void m(); } +abstract class B_2 implements D2 {} + +// B_.m, C.m +abstract class A_3 extends B_3 implements C3 {} +abstract class B_3 implements D3 { public abstract void m(); } + +// B_.m, D.m +abstract class A_4 extends B_4 implements C4 {} +abstract class B_4 implements D4 { public abstract void m(); } + public class StarInheritance { private static int n = 1; - private static void test(Method [] ma, ArrayList expect) { + private static void test(Method [] ma, ArrayList> expect) { + // filter out Object methods + List ml = Stream.of(ma) + .filter(m -> m.getDeclaringClass() != Object.class) + .collect(Collectors.toList()); + System.out.println("Test " + n++); - if (expect.size() != ma.length) { - System.err.println(" found methods: " + Arrays.asList(ma)); + if (expect.size() != ml.size()) { + System.err.println(" found methods: " + ml); System.err.println(" expected locations: " + expect); - throw new RuntimeException("found = " + ma.length + throw new RuntimeException("found = " + ml.size() +"; expected = " + expect.size()); } - for (int i = 0; i < ma.length; i++) { - Method m = ma[i]; + for (Method m : ml) { System.out.println(" " + m.toString()); int n; if (m.getName().equals("m") @@ -83,16 +106,28 @@ } public static void main(String [] args) { - Class [] l1 = {D1.class}; - test(A1.class.getMethods(), new ArrayList(Arrays.asList(l1))); + Class [] l1 = {D1.class}; + test(A1.class.getMethods(), new ArrayList<>(Arrays.asList(l1))); + + Class [] l2 = {A2.class}; + test(A2.class.getMethods(), new ArrayList<>(Arrays.asList(l2))); + + Class [] l3 = {B3.class, C3.class}; + test(A3.class.getMethods(), new ArrayList<>(Arrays.asList(l3))); + + Class [] l4 = {B4.class, D4.class}; + test(A4.class.getMethods(), new ArrayList<>(Arrays.asList(l4))); + + Class [] l_1 = {D1.class}; + test(A_1.class.getMethods(), new ArrayList<>(Arrays.asList(l_1))); - Class [] l2 = {A2.class}; - test(A2.class.getMethods(), new ArrayList(Arrays.asList(l2))); + Class [] l_2 = {A_2.class}; + test(A_2.class.getMethods(), new ArrayList<>(Arrays.asList(l_2))); - Class [] l3 = {B3.class, C3.class}; - test(A3.class.getMethods(), new ArrayList(Arrays.asList(l3))); + Class [] l_3 = {B_3.class, C3.class}; + test(A_3.class.getMethods(), new ArrayList<>(Arrays.asList(l_3))); - Class [] l4 = {B4.class, D4.class}; - test(A4.class.getMethods(), new ArrayList(Arrays.asList(l4))); + Class [] l_4 = {B_4.class, D4.class}; + test(A_4.class.getMethods(), new ArrayList<>(Arrays.asList(l_4))); } } --- /dev/null 2014-11-30 10:11:31.639805301 +0100 +++ new/src/java.base/share/classes/java/lang/HashArray.java 2014-11-30 18:51:42.093547293 +0100 @@ -0,0 +1,346 @@ +/* + * Copyright (c) 1994, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +import java.util.Arrays; + +/** + * Linear-probe hash table. + *

+ * Modeled by the {@link java.util.IdentityHashMap}, but using overriddable + * {@link #equals(Object, Object) equals} / {@link #hashCode(Object) hashCode} + * to compare/locate elements. The API of this class is similar + * in function and form to {@link java.util.Map}, but doesn't use separate keys + * and values. In this API an element is a key and a value at the same time. + * An element is always non-null, so the return values are unambiguous. + *

+ * Because this is a linear-probe hash table and there are no + * {@link java.util.Map.Entry} objects involved, the underlying + * data structure is very simple and memory efficient. + * It is just a sparse array of elements with length that + * is always a power of two and larger than 3 * {@link #size()} / 2. + * + * @param Type of elements contained in the HashArray. + */ +class HashArray { + /** + * The minimum capacity, used if a lower value is implicitly specified. + * The value 2 corresponds to an expected maximum size of 1, + * given a load factor of 2/3. MUST be a power of two. + */ + private static final int MINIMUM_CAPACITY = 2; + + /** + * The maximum capacity. + *

+ * In fact, the HashArray can hold no more than MAXIMUM_CAPACITY-1 elements + * because it has to have at least one slot == null + * in order to avoid infinite loops in get() and put(). + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The table, re-sized as necessary. + * Length MUST always be a power of two. + */ + private T[] table; + + /** + * The number of elements contained in this HashArray. + */ + private int size; + + /** + * Constructor with {@code expectedSize} pre-allocates the + * {@link #table}. + * + * @param expectedMaxSize expected number of elements new HashArray + * will hold. + */ + @SuppressWarnings("unchecked") + public HashArray(int expectedMaxSize) { + if (expectedMaxSize < 0) + throw new IllegalArgumentException("expectedMaxSize is negative: " + + expectedMaxSize); + table = (T[]) new Object[capacity(expectedMaxSize)]; + } + + /** + * Returns the appropriate capacity for the given expected maximum size. + * Returns the smallest power of two between MINIMUM_CAPACITY and + * MAXIMUM_CAPACITY, inclusive, that is greater than (3 * + * expectedMaxSize)/2, if such a number exists. Otherwise returns + * MAXIMUM_CAPACITY. + */ + private static int capacity(int expectedMaxSize) { + // assert expectedMaxSize >= 0; + return + (expectedMaxSize > MAXIMUM_CAPACITY / 3) ? MAXIMUM_CAPACITY : + (expectedMaxSize <= 2 * MINIMUM_CAPACITY / 3) ? MINIMUM_CAPACITY : + Integer.highestOneBit(expectedMaxSize + (expectedMaxSize << 1)); + } + + /** + * Returns a hash code value for an element of this HashArray or a + * lookup object. + *

+ * By default it returns: + *

+     *     obj.{@link Object#hashCode() hashCode()}
+     * 
+ * But can be overridden in subclasses. + * + * @param obj an element of this HashArray or a lookup object + * @return a hash code value for an element or lookup object. + * @see #equals(Object, Object) + */ + protected int hashCode(Object obj) { + return obj.hashCode(); + } + + /** + * Indicates whether an element of this HashArray is "equal to" + * a lookup object or another element. + *

+ * By default it returns: + *

+     *     element.{@link Object#equals(Object) equals(obj)}
+     * 
+ * But can be overridden in subclasses. + * + * @param element an element of this HashArray + * @param obj a lookup object or another element + * @return {@code true} if an element is the same + * as a lookup object or another element; + * {@code false} otherwise. + * @see #hashCode(Object) + */ + protected boolean equals(T element, Object obj) { + return element.equals(obj); + } + + /** + * Returns index for Object x. + */ + private int hash(Object x, int length) { + int h = hashCode(x); + return (h ^ (h >>> 16)) & (length - 1); + } + + /** + * Circularly traverses table of size length (which is a power of 2). + */ + private static int nextKeyIndex(int i, int length) { + return (i + 1) & (length - 1); + } + + /** + * @return Number of elements contained in this HashArray. + */ + public int size() { + return size; + } + + /** + * @param lookupObj a non-null lookup object + * @return The element which is equal to specified {@code lookupObj} + * if it exists in this HashArray or null if it doesn't. + * (There can be at most one such element.) + * @throws NullPointerException if {@code lookupObj} is null + */ + @SuppressWarnings("unchecked") + public T get(Object lookupObj) { + if (lookupObj == null) throw new NullPointerException(); + final T[] tab = table; + int i = hash(lookupObj, tab.length); + while (true) { + T element = tab[i]; + if (element == null) { + return null; + } + if (equals(element, lookupObj)) { + return element; + } + i = nextKeyIndex(i, tab.length); + } + } + + /** + * Adds {@code newElement} if an element equal to it is not present in + * this HashArray and returns null, or returns the existing element and + * replaces it with {@code newElement} if such element is present. + * + * @param newElement new element to put into this HashArray + * @return previous element if there was one or null if there was none + */ + public T put(T newElement) { + return put(newElement, true); + } + + /** + * Adds {@code newElement} if an element equal to it is not present in + * this HashArray and returns null, or returns the existing element and + * doesn't modify HashArray if such element is present. + * + * @param newElement new element to put into this HashArray + * @return previous element if there was one or null if there was none + */ + public T putIfAbsent(T newElement) { + return put(newElement, false); + } + + private T put(T newElement, boolean replace) { + if (newElement == null) throw new NullPointerException(); + for (; ; ) { + final T[] tab = table; + int i = hash(newElement, tab.length); + + for ( + T element; (element = tab[i]) != null; + i = nextKeyIndex(i, tab.length) + ) { + if (equals(element, newElement)) { + if (replace) { + tab[i] = newElement; + } + return element; + } + } + + final int s = size + 1; + // Use optimized form of 3 * s / 2. + // Next capacity is 2 * current capacity. + if (s + (s >> 1) > tab.length && resize(tab.length << 1)) + continue; + + tab[i] = newElement; + size = s; + return null; + } + } + + /** + * Resizes the table if necessary to hold given capacity. + */ + private boolean resize(int newLength) { + T[] oldTable = table; + int oldLength = oldTable.length; + if (oldLength == MAXIMUM_CAPACITY) { // can't expand any further + if (size == MAXIMUM_CAPACITY - 1) + throw new IllegalStateException("Capacity exhausted."); + return false; + } + if (oldLength >= newLength) + return false; + + @SuppressWarnings("unchecked") + T[] newTable = (T[]) new Object[newLength]; + + for (int j = 0; j < oldLength; j++) { + T element = oldTable[j]; + if (element != null) { + oldTable[j] = null; + int i = hash(element, newLength); + while (newTable[i] != null) + i = nextKeyIndex(i, newLength); + newTable[i] = element; + } + } + table = newTable; + return true; + } + + /** + * Removes the element equal to {@code lookupObj} and + * returns it if present or returns null if not present. + * + * @param lookupObj a non-null lookup object + * @return the removed element if there was one or null if + * there was none + * @throws NullPointerException if {@code lookupObj} is null + */ + public T remove(Object lookupObj) { + if (lookupObj == null) throw new NullPointerException(); + T[] tab = table; + int i = hash(lookupObj, tab.length); + + while (true) { + T element = tab[i]; + if (element == null) { + return null; + } + if (equals(element, lookupObj)) { + size--; + tab[i] = null; + closeDeletion(tab, i); + return element; + } + i = nextKeyIndex(i, tab.length); + } + } + + /** + * Rehash all possibly-colliding elements following a + * deletion. This preserves the linear-probe + * collision properties required by get, putIfAbsent, remove. + * + * @param d the index of a newly empty deleted slot + */ + private void closeDeletion(Object[] tab, int d) { + // Adapted from Knuth Section 6.4 Algorithm R + + // Look for elements to swap into newly vacated slot + // starting at index immediately following deletion, + // and continuing until a null slot is seen, indicating + // the end of a run of possibly-colliding elements. + Object element; + for ( + int i = nextKeyIndex(d, tab.length); (element = tab[i]) != null; + i = nextKeyIndex(i, tab.length) + ) { + // The following test triggers if the element at slot i (which + // hashes to be at slot r) should take the spot vacated by d. + // If so, we swap it in, and then continue with d now at the + // newly vacated i. This process will terminate when we hit + // the null slot at the end of this run. + // The test is messy because we are using a circular table. + int r = hash(element, tab.length); + if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) { + tab[d] = element; + tab[i] = null; + d = i; + } + } + } + + /** + * Removes all of the elements from this HashArray. + */ + public void clear() { + Arrays.fill(table, null); + size = 0; + } +} --- /dev/null 2014-11-30 10:11:31.639805301 +0100 +++ new/src/java.base/share/classes/java/lang/MethodTable.java 2014-11-30 18:51:42.361542562 +0100 @@ -0,0 +1,612 @@ +/* + * Copyright (c) 1994, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +import sun.reflect.ReflectionFactory; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A table of methods with different operations for adding and removing methods. + * It implements {@link Iterable} which iterates {@link java.lang.reflect.Method} objects + * in order they were added to the table. + */ +interface MethodTable extends Iterable { + + /** + * When requested {@code expectedMaxSize <= MAX_ARRAY_SIZE} then + * {@link java.lang.MethodTable.SimpleArrayImpl} is created + * else {@link HashArrayImpl} is created. + */ + int MAX_ARRAY_SIZE = 20; + + /** + * Constructs new instance of MethodTable with enough capacity to + * hold {@code expectedMaxSize} methods. + * + * @param expectedMaxSize expected maximum size of MethodTable + * @return new instance of MethodTable + */ + static MethodTable newInstance(int expectedMaxSize) { + return expectedMaxSize <= MAX_ARRAY_SIZE + ? new SimpleArrayImpl(expectedMaxSize) + : new HashArrayImpl(expectedMaxSize); + } + + /** + * Unconditionally adds given {@code method} to this MethodTable. + * + * @param method a method to add + */ + void add(Method method); + + /** + * Adds given {@code method} to this MethodTable unless a method + * with same signature already exists and it's + * {@link java.lang.reflect.Method#getDeclaringClass() declaring class} + * is equal to given {@code declaringClass}. + * + * @param method a method to add + * @param declaringClass the class to check against declaring classes + * of existing methods with same signature + */ + void addUnlessDeclaredExists(Method method, Class declaringClass); + + /** + * Each existing method with same signature as given {@code method} + * is compared in turn with it and the following actions are taken: + *
    + *
  • if given method is same method as existing method, + * the operation completes without adding given method.
  • + *
  • if existing method is declared by given {@code declaringClass}, + * the operation completes without adding given method.
  • + *
  • if existing method is not abstract and either existing method + * overrides given method or existing method is declared by class and given + * method is declared by interface, the operation completes without + * adding given method.
  • + *
  • if given method is not abstract and either given method overrides + * existing method or given method is declared by class and existing + * method is declared by interface, then existing method is removed + * from this MethodTable.
  • + *
  • if there are no more existing methods then given method is added + * to this MethodTable and operation completes else next existing + * method is taken into consideration.
  • + *
+ * + * @param method a method to consolidate with existing methods of same + * signature + */ + void consolidate(Method method, Class declaringClass); + + /** + * @return the size of this MethodTable + */ + int getSize(); + + /** + * Clears this MethodTable. + */ + void clear(); + + /** + * @return new array of methods contained in this MethodTable + * in order they were added to this table. + */ + default Method[] getMethods() { + Method[] methods = new Method[getSize()]; + int i = 0; + for (Method m : this) { + methods[i++] = m; + } + return methods; + } + + + /** + * @return the 1st among the methods with the most specific return type + * if there is one or null if there are no methods in this MethodTable. + */ + default Method getFirstMethodWithMostSpecificReturnType() { + Method res = null; + for (Method m : this) { + if (res == null || ( + res.getReturnType() != m.getReturnType() && + res.getReturnType().isAssignableFrom(m.getReturnType()) + )) { + res = m; + } + } + return res; + } + + /** + * A doubly-linked list of nodes. Each DLNode starts life as + * it's own doubly-linked singleton list.

+ * {@link #unlink(Anchor anchor)} un-links a DLNode from a doubly-linked + * list with other nodes and liberates it so that it forms it's + * own singleton list. It also decrements {@code anchor}'s size.

+ * {@link #linkBefore(Anchor anchor)} inserts a DLNode into the doubly-linked + * list before the {@code anchor} and increments it's size. + */ + interface DLNode { + DLNode getNextDln(); + + void setNextDln(DLNode nextDln); + + DLNode getPrevDln(); + + void setPrevDln(DLNode prevDln); + + /** + * Insert {@code this} DLNode before {@code anchor} + */ + default void linkBefore(Anchor anchor) { + // should be free before linking + assert this.getNextDln() == this && + this.getPrevDln() == this; + DLNode prev = anchor.getPrevDln(); + this.setNextDln(anchor); + anchor.setPrevDln(this); + prev.setNextDln(this); + this.setPrevDln(prev); + anchor.addToSize(1); + } + + /** + * Remove {@code this} DLNode from doubly-linked list + */ + default void unlink(Anchor anchor) { + DLNode next = this.getNextDln(); + DLNode prev = this.getPrevDln(); + // should be linked before un-linking + assert next != this && prev != this; + next.setPrevDln(prev); + prev.setNextDln(next); + this.setNextDln(this); + this.setPrevDln(this); + anchor.addToSize(-1); + } + + /** + * Anchor is a {@link DLNode} which keeps track + * of doubly-linked list size. + */ + interface Anchor extends DLNode { + void addToSize(int increment); + } + } + + /** + * A node forming a linked-list of Method objects that share the same signature + * and a key with equals/hashCode that compares method signature. + * It also implements {@link DLNode} so it forms a separate doubly-linked list + * with other DLNode(s) to keep insertion order. + */ + final class MethodNode implements DLNode { + final Method method; + private final int hash; + MethodNode next; + + MethodNode(Method method) { + this.method = method; + this.hash = method.getReturnType().hashCode() ^ + method.getName().hashCode() ^ + reflectionFactory.methodParameterTypesHashCode(method); + } + + void add(MethodNode other, Anchor anchor) { + other.next = this.next; + this.next = other; + other.linkBefore(anchor); + } + + void addUnlessDeclaredExists(MethodNode other, + Class declaringClass, + Anchor anchor) { + for (MethodNode node = this; + node.method.getDeclaringClass() != declaringClass; + node = node.next + ) { + if (node.next == null) { + node.next = other; + other.linkBefore(anchor); + break; + } + } + } + + MethodNode consolidate(MethodNode other, + Class declaringClass, + Anchor anchor) { + Class thisCl = this.method.getDeclaringClass(); + Class otherCl = other.method.getDeclaringClass(); + + if (thisCl == otherCl || // this.method is the same as other.method OR + thisCl == declaringClass) { // this.method is declared by declaringClass + // complete without adding other + return this; + } + + boolean thisAbstract = Modifier.isAbstract(this.method.getModifiers()); + + if (!thisAbstract && // this.method is non-abstract... + otherCl.isAssignableFrom(thisCl)) { // ...and overrides other.method + // complete without adding other + return this; + } + + boolean thisInterface = thisCl.isInterface(); + boolean otherInterface = otherCl.isInterface(); + + if (!thisAbstract && !thisInterface && // this.method is non-abstract class method... + otherInterface) { //...which wins over interface other.method + // complete without adding other + return this; + } + + boolean otherAbstract = Modifier.isAbstract(other.method.getModifiers()); + + if (!otherAbstract && // other.method is non-abstract... + thisCl.isAssignableFrom(otherCl)) { // ...and overrides this.method + // remove this and... + this.unlink(anchor); + if (next == null) { // ...add other + other.linkBefore(anchor); + return other; + } else { // ...continue with other + return next.consolidate(other, declaringClass, anchor); + } + } + + if (!otherAbstract && !otherInterface && // other.method is non-abstract class method... + thisInterface) { //...which wins over interface this.method + // remove this and... + this.unlink(anchor); + if (next == null) { // ...add other + other.linkBefore(anchor); + return other; + } else { // ...continue with other + return next.consolidate(other, declaringClass, anchor); + } + } + + // else keep this and... + if (next == null) { // ...add other + other.linkBefore(anchor); + next = other; + } else { //...continue with other + next = next.consolidate(other, declaringClass, anchor); + } + return this; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof MethodNode)) return false; + MethodNode other = (MethodNode) obj; + return this.method.getReturnType() == other.method.getReturnType() && + this.method.getName() == other.method.getName() && // Method.name is interned + reflectionFactory.methodParameterTypesEquals(this.method, other.method); + } + + // we just need to compare method parameter types + + private static final ReflectionFactory reflectionFactory + = AccessController.doPrivileged(new ReflectionFactory.GetReflectionFactoryAction()); + + // DLNode implementation + + private DLNode nextDln = this; + private DLNode prevDln = this; + + @Override + public DLNode getNextDln() { + return nextDln; + } + + @Override + public void setNextDln(DLNode nextDln) { + this.nextDln = nextDln; + } + + @Override + public DLNode getPrevDln() { + return prevDln; + } + + @Override + public void setPrevDln(DLNode prevDln) { + this.prevDln = prevDln; + } + } + + /** + * MethodTable implementation based on {@link HashArray}. + * It also implements {@link DLNode.Anchor} to + * form a doubly-linked list with other DLNode(s). + */ + final class HashArrayImpl extends HashArray implements MethodTable, DLNode.Anchor { + + HashArrayImpl(int expectedMaxSize) { + super(expectedMaxSize); + } + + @Override + public void add(Method method) { + MethodNode n = new MethodNode(method); + MethodNode n0 = putIfAbsent(n); + if (n0 == null) { + n.linkBefore(this); + } else { + n0.add(n, this); + } + } + + @Override + public void addUnlessDeclaredExists(Method method, Class declaringClass) { + MethodNode n = new MethodNode(method); + MethodNode n0 = putIfAbsent(n); + if (n0 == null) { + n.linkBefore(this); + } else { + n0.addUnlessDeclaredExists(n, declaringClass, this); + } + } + + @Override + public void consolidate(Method method, Class declaringClass) { + MethodNode n = new MethodNode(method); + MethodNode n0 = putIfAbsent(n); + if (n0 == null) { + n.linkBefore(this); + } else { + MethodNode n1 = n0.consolidate(n, declaringClass, this); + if (n1 != n0) { + put(n1); + } + } + } + + @Override + public int getSize() { + return size; + } + + @Override + public void clear() { + super.clear(); + nextDln = prevDln = this; + size = 0; + } + + // Iterable implementation + + @Override + public Iterator iterator() { + return new Iterator() { + + DLNode dln = HashArrayImpl.this.getNextDln(); + + @Override + public boolean hasNext() { + return dln != HashArrayImpl.this; + } + + @Override + public Method next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Method result = ((MethodNode) dln).method; + dln = dln.getNextDln(); + return result; + } + }; + } + + // DLNode.Anchor implementation + + private DLNode nextDln = this; + private DLNode prevDln = this; + private int size; + + @Override + public DLNode getNextDln() { + return nextDln; + } + + @Override + public void setNextDln(DLNode nextDln) { + this.nextDln = nextDln; + } + + @Override + public DLNode getPrevDln() { + return prevDln; + } + + @Override + public void setPrevDln(DLNode prevDln) { + this.prevDln = prevDln; + } + + @Override + public void addToSize(int increment) { + size += increment; + } + } + + /** + * Simple array based MethodTable implementation for small number of methods. + */ + final class SimpleArrayImpl implements MethodTable { + + private final Method[] methods; + private int hwm; // High Water Mark + + SimpleArrayImpl(int expectedMaxSize) { + methods = new Method[expectedMaxSize]; + } + + @Override + public void add(Method method) { + methods[hwm++] = method; + } + + @Override + public void addUnlessDeclaredExists(Method otherM, Class declaringClass) { + for (int i = 0; i < hwm; i++) { + Method thisM = methods[i]; + if (thisM == null || // mind the gap + thisM.getReturnType() != otherM.getReturnType() || // not same signature + thisM.getName() != otherM.getName() || // Method.name is interned + !MethodNode.reflectionFactory.methodParameterTypesEquals(thisM, otherM)) { + continue; + } + // thisM has same signature as otherM + + if (thisM.getDeclaringClass() == declaringClass) { + return; + } + // continue with next thisM + } + // add otherM + methods[hwm++] = otherM; + } + + @Override + public void consolidate(Method otherM, Class declaringClass) { + for (int i = 0; i < hwm; i++) { + Method thisM = methods[i]; + if (thisM == null || // mind the gap + thisM.getReturnType() != otherM.getReturnType() || // not same signature + thisM.getName() != otherM.getName() || // Method.name is interned + !MethodNode.reflectionFactory.methodParameterTypesEquals(thisM, otherM)) { + continue; + } + // thisM has same signature as otherM + + Class thisCl = thisM.getDeclaringClass(); + Class otherCl = otherM.getDeclaringClass(); + + if (thisCl == otherCl || // thisM is the same as otherM OR + thisCl == declaringClass) { // thisM is declared by declaringClass + // complete without adding otherM + return; + } + + boolean thisAbstract = Modifier.isAbstract(thisM.getModifiers()); + + if (!thisAbstract && // thisM is non-abstract... + otherCl.isAssignableFrom(thisCl)) { // ...and overrides otherM + // complete without adding otherM + return; + } + + boolean thisInterface = thisCl.isInterface(); + boolean otherInterface = otherCl.isInterface(); + + if (!thisAbstract && !thisInterface && // this.method is non-abstract class method... + otherInterface) { //...which wins over interface other.method + // complete without adding otherM + return; + } + + boolean otherAbstract = Modifier.isAbstract(otherM.getModifiers()); + + if (!otherAbstract && // other.method is non-abstract... + thisCl.isAssignableFrom(otherCl)) { // ...and overrides this.method + // remove this + methods[i] = null; + continue; + } + + if (!otherAbstract && !otherInterface && // other.method is non-abstract class method... + thisInterface) { //...which wins over interface this.method + // remove this + methods[i] = null; + } + // continue with next thisM + } + // add otherM + methods[hwm++] = otherM; + } + + @Override + public int getSize() { + int size = 0; + for (int i = 0; i < hwm; i++) { + if (methods[i] != null) { + size++; + } + } + return size; + } + + @Override + public void clear() { + Arrays.fill(methods, null); + hwm = 0; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int i = 0; + Method next; + + private Method nextMethod() { + while (next == null && i < hwm) next = methods[i++]; + return next; + } + + @Override + public boolean hasNext() { + return nextMethod() != null; + } + + @Override + public Method next() { + Method m = nextMethod(); + if (m == null) throw new NoSuchElementException(); + next = null; + return m; + } + }; + } + } +} --- /dev/null 2014-11-30 10:11:31.639805301 +0100 +++ new/test/java/lang/Class/getMethods/HashArrayTest.java 2014-11-30 18:51:42.597538397 +0100 @@ -0,0 +1,198 @@ +/* + * Copyright (c) 1994, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.testlibrary.TestProxy; + +import java.util.Objects; + +/** + * @test + * @summary Unit tests for java.lang.HashArray internal class. + */ +public class HashArrayTest { + + public static void main(String[] args) { + + testPut(); + testPutIfAbsent(); + testRemove(); + + System.out.println("All tests passed."); + } + + @SuppressWarnings("UnnecessaryBoxing") + static final Integer i11 = new Integer(1); + + @SuppressWarnings("UnnecessaryBoxing") + static final Integer i12 = new Integer(1); + + @SuppressWarnings("UnnecessaryBoxing") + static final Integer i21 = new Integer(2); + + @SuppressWarnings("UnnecessaryBoxing") + static final Integer i22 = new Integer(2); + + static void testPut() { + HashArray ha = newHashArray(); + + assertSameObject(null, ha.put(i11)); + assertEquals(1, ha.size()); + + assertSameObject(i11, ha.get(i11)); + assertSameObject(i11, ha.get(i12)); + + assertSameObject(i11, ha.put(i12)); + assertEquals(1, ha.size()); + + assertSameObject(i12, ha.get(i11)); + assertSameObject(i12, ha.get(i12)); + + assertSameObject(null, ha.put(i21)); + assertEquals(2, ha.size()); + + assertSameObject(i21, ha.get(i21)); + assertSameObject(i21, ha.get(i22)); + + assertSameObject(i21, ha.put(i22)); + assertEquals(2, ha.size()); + + assertSameObject(i22, ha.get(i21)); + assertSameObject(i22, ha.get(i22)); + } + + static void testPutIfAbsent() { + HashArray ha = newHashArray(); + + assertSameObject(null, ha.putIfAbsent(i11)); + assertEquals(1, ha.size()); + + assertSameObject(i11, ha.get(i11)); + assertSameObject(i11, ha.get(i12)); + + assertSameObject(i11, ha.putIfAbsent(i12)); + assertEquals(1, ha.size()); + + assertSameObject(i11, ha.get(i11)); + assertSameObject(i11, ha.get(i12)); + + assertSameObject(null, ha.putIfAbsent(i21)); + assertEquals(2, ha.size()); + + assertSameObject(i21, ha.get(i21)); + assertSameObject(i21, ha.get(i22)); + + assertSameObject(i21, ha.putIfAbsent(i22)); + assertEquals(2, ha.size()); + + assertSameObject(i21, ha.get(i21)); + assertSameObject(i21, ha.get(i22)); + } + + static void testRemove() { + HashArray ha = newHashArray(); + + assertSameObject(null, ha.put(i11)); + assertSameObject(null, ha.put(i21)); + assertEquals(2, ha.size()); + + assertSameObject(i11, ha.remove(i11)); + assertSameObject(null, ha.get(i11)); + assertEquals(1, ha.size()); + + assertSameObject(i21, ha.remove(i21)); + assertSameObject(null, ha.get(i21)); + assertEquals(0, ha.size()); + + // once again with equal elements + + assertSameObject(null, ha.put(i12)); + assertSameObject(null, ha.put(i22)); + assertEquals(2, ha.size()); + + assertSameObject(i12, ha.remove(i11)); + assertSameObject(null, ha.get(i11)); + assertEquals(1, ha.size()); + + assertSameObject(i22, ha.remove(i21)); + assertSameObject(null, ha.get(i21)); + assertEquals(0, ha.size()); + } + + + // test infrastructure + + static HashArray newHashArray() { + HashArray ha = HashArray.newInstance(1); + assertSameObject(null, ha.get(i11)); + assertSameObject(null, ha.get(i12)); + assertSameObject(null, ha.get(i21)); + assertSameObject(null, ha.get(i22)); + assertEquals(0, ha.size()); + return ha; + } + + static void assertSameObject(Object expected, Object actual) { + if (expected != actual) { + throw new AssertionError("Expected:\n " + expected + + "\nActual:\n " + actual); + + } + } + + static void assertEquals(Object expected, Object actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected:\n " + expected + + "\nActual:\n " + actual); + + } + } + + // test proxy for package-private java.lang.HashArray + + interface HashArray { + + @SuppressWarnings("unchecked") + public static HashArray newInstance(int maxExpectedSize) { + return (HashArray) + TestProxy.newInstance(HashArray.class, + "java.lang.HashArray", + ClassLoader.getSystemClassLoader(), + new Class[]{int.class}, + maxExpectedSize); + } + + public int size(); + + public T get(Object lookupObj); + + public T put(T newElement); + + public T putIfAbsent(T newElement); + + public T remove(Object lookupObj); + + public void clear(); + } +} --- /dev/null 2014-11-30 10:11:31.639805301 +0100 +++ new/test/java/lang/Class/getMethods/MethodTableTest.java 2014-11-30 18:51:42.894533155 +0100 @@ -0,0 +1,352 @@ +/* + * Copyright (c) 1994, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.testlibrary.TestProxy; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.function.Function; + +/** + * @test + * @summary Unit tests for java.lang.MethodTable internal implementations. + */ +public class MethodTableTest { + + public static void main(String[] args) { + + testAdd(MethodTable::newSimpleArrayImpl); + testAddUnlessDeclaredExists(MethodTable::newSimpleArrayImpl); + testConsolidate(MethodTable::newSimpleArrayImpl); + testGetFirstMethodWithMostSpecificReturnType(MethodTable::newSimpleArrayImpl); + + testAdd(MethodTable::newHashArrayImpl); + testAddUnlessDeclaredExists(MethodTable::newHashArrayImpl); + testConsolidate(MethodTable::newHashArrayImpl); + testGetFirstMethodWithMostSpecificReturnType(MethodTable::newHashArrayImpl); + + System.out.println("All tests passed."); + } + + // MethodTable.add + + static void testAdd(Method[] methods, + Function mtFactory) { + MethodTable mt = mtFactory.apply(methods.length); + assertEmpty(mt); + for (Method m : methods) mt.add(m); + assertSameObjects(methods, mt.getMethods()); + } + + static void testAdd(Function mtFactory) { + Method[] methods = A.class.getDeclaredMethods(); + + testAdd(methods, mtFactory); + + // repeated sequence + Method[] methods2 = concat(methods, methods); + + testAdd(methods2, mtFactory); + } + + // MethodTable.addUnlessDeclaredExists + + static void testAddUnlessDeclaredExists(Method[] methods, + Class declaringClass, + Method[] expectedResult, + Function mtFactory) { + MethodTable mt = mtFactory.apply(methods.length); + assertEmpty(mt); + for (Method m : methods) mt.addUnlessDeclaredExists(m, declaringClass); + assertSameObjects(expectedResult, mt.getMethods()); + } + + static void testAddUnlessDeclaredExists(Function mtFactory) { + Method[] methodsA = A.class.getDeclaredMethods(); + Method[] methodsB = B.class.getDeclaredMethods(); + + testAddUnlessDeclaredExists(methodsA, A.class, methodsA, mtFactory); + + // concatenated methods + Method[] methodsAB = concat(methodsA, methodsB); + Method[] methodsBA = concat(methodsB, methodsA); + + testAddUnlessDeclaredExists(methodsAB, Object.class, methodsAB, mtFactory); + testAddUnlessDeclaredExists(methodsAB, A.class, methodsA, mtFactory); + testAddUnlessDeclaredExists(methodsAB, B.class, methodsAB, mtFactory); + + testAddUnlessDeclaredExists(methodsBA, Object.class, methodsBA, mtFactory); + testAddUnlessDeclaredExists(methodsBA, A.class, methodsBA, mtFactory); + testAddUnlessDeclaredExists(methodsBA, B.class, methodsB, mtFactory); + } + + // MethodTable.consolidate + + static void testConsolidate(Method[] methods, + Class declaringClass, + Method[] expectedResult, + Function mtFactory) { + MethodTable mt = mtFactory.apply(methods.length); + assertEmpty(mt); + for (Method m : methods) mt.consolidate(m, declaringClass); + assertSameObjects(expectedResult, mt.getMethods()); + } + + static void testConsolidate(Function mtFactory) { + Method[] methodsA = A.class.getDeclaredMethods(); + Method[] methodsB = B.class.getDeclaredMethods(); + Method[] methodsAX = AX.class.getDeclaredMethods(); + Method[] methodsI = I.class.getDeclaredMethods(); + Method[] methodsX = X.class.getDeclaredMethods(); + Method[] methodsY = Y.class.getDeclaredMethods(); + Method[] methodsZ = Z.class.getDeclaredMethods(); + + testConsolidate(methodsA, A.class, methodsA, mtFactory); + + // if given method is same method as existing method, + // the operation completes without adding given method. + testConsolidate(concat(methodsA, methodsA), Object.class, methodsA, mtFactory); + + // concatenated methods + Method[] methodsAB = concat(methodsA, methodsB); + Method[] methodsBA = concat(methodsB, methodsA); + + // if existing method is declared by given declaringClass, + // the operation completes without adding given method. + testConsolidate(methodsAB, A.class, methodsA, mtFactory); + testConsolidate(methodsBA, B.class, methodsB, mtFactory); + testConsolidate(methodsAB, Object.class, methodsAB, mtFactory); + testConsolidate(methodsBA, Object.class, methodsBA, mtFactory); + + // if existing method is not abstract and either existing method + // overrides given method... + testConsolidate(concat(methodsAX, methodsA), Object.class, methodsAX, mtFactory); + + // ...or existing method is declared by class + // and given method is declared by interface, the operation completes + // without adding given method. + testConsolidate(concat(methodsA, methodsI), Object.class, methodsA, mtFactory); + + // if given method is not abstract and either given method overrides existing + // method... + testConsolidate(concat(methodsA, methodsAX), Object.class, methodsAX, mtFactory); + + // ...or given method is declared by class and existing method is declared + // by interface, then existing method is removed from this MethodTable... + testConsolidate(concat(methodsI, methodsA), Object.class, methodsA, mtFactory); + + // ...if there are no more existing methods then given method is added to this + // MethodTable and operation completes else next existing method is taken + // into consideration. + testConsolidate(concat(methodsX, methodsY, methodsZ), Object.class, + concat(methodsX, methodsY, methodsZ), mtFactory); + } + + // + + static void testGetFirstMethodWithMostSpecificReturnType(Method[] methods, + Method expectedResult, + Function mtFactory) { + MethodTable mt = mtFactory.apply(methods.length); + assertEmpty(mt); + for (Method m : methods) mt.add(m); + assertSameObject(expectedResult, mt.getFirstMethodWithMostSpecificReturnType()); + } + + static void testGetFirstMethodWithMostSpecificReturnType(Function mtFactory) { + Method[] methodsX = X.class.getDeclaredMethods(); + Method[] methodsY = Y.class.getDeclaredMethods(); + Method[] methodsZ = Z.class.getDeclaredMethods(); + + testGetFirstMethodWithMostSpecificReturnType(methodsX, methodsX[0], mtFactory); + + testGetFirstMethodWithMostSpecificReturnType(concat(methodsX, methodsY), methodsY[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsY, methodsX), methodsY[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsY, methodsZ), methodsY[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsZ, methodsY), methodsZ[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsX, methodsZ), methodsZ[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsZ, methodsX), methodsZ[0], mtFactory); + + testGetFirstMethodWithMostSpecificReturnType(concat(methodsX, methodsY, methodsZ), methodsY[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsX, methodsZ, methodsY), methodsZ[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsY, methodsX, methodsZ), methodsY[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsZ, methodsX, methodsY), methodsZ[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsY, methodsZ, methodsX), methodsY[0], mtFactory); + testGetFirstMethodWithMostSpecificReturnType(concat(methodsZ, methodsY, methodsX), methodsZ[0], mtFactory); + } + + // test infrastructure + + static void assertEmpty(MethodTable mt) { + if (mt.getSize() != 0) { + throw new AssertionError("MethodTable is not empty"); + } + } + + static void assertSameObject(Object expected, Object actual) { + if (expected != actual) { + throw new AssertionError("Expected:\n " + expected + + "\nActual:\n " + actual); + + } + } + + static void assertSameObjects(Object[] expected, Object[] actual) { + boolean ok = (expected.length == actual.length); + if (ok) { + for (int i = 0; ok && i < expected.length; i++) { + ok = (expected[i] == actual[i]); + } + } + if (!ok) { + throw new AssertionError("Expected:\n " + Arrays.toString(expected) + + "\nActual:\n " + Arrays.toString(actual)); + } + } + + static Method[] concat(Method[]... mas) { + int len = 0; + for (Method[] ma : mas) { + len += ma.length; + } + Method[] cma = new Method[len]; + int offset = 0; + for (Method[] ma : mas) { + System.arraycopy(ma, 0, cma, offset, ma.length); + offset += ma.length; + } + return cma; + } + + static class A { + public void m() { + } + + public void m(int i) { + } + + public void m(long l) { + } + + public void m(Object o) { + } + + public void m(String s) { + } + } + + static class AX extends A { + public void m() { + } + + public void m(int i) { + } + + public void m(long l) { + } + + public void m(Object o) { + } + + public void m(String s) { + } + } + + static class B { + public void m() { + } + + public void m(int i) { + } + + public void m(long l) { + } + + public void m(Object o) { + } + + public void m(String s) { + } + } + + interface I { + void m(); + + void m(int i); + + void m(long l); + + void m(Object o); + + void m(String s); + } + + interface X { + Object m(); + } + + static abstract class Y { + public abstract Number m(); + } + + interface Z { + Number m(); + } + + // test proxy for package-private java.lang.MethodTable implementations + + interface MethodTable extends Iterable { + + static MethodTable newSimpleArrayImpl(int maxExpectedSize) { + return TestProxy.newInstance(MethodTable.class, + "java.lang.MethodTable$SimpleArrayImpl", + ClassLoader.getSystemClassLoader(), + new Class[]{int.class}, + maxExpectedSize); + } + + static MethodTable newHashArrayImpl(int maxExpectedSize) { + return TestProxy.newInstance(MethodTable.class, + "java.lang.MethodTable$HashArrayImpl", + ClassLoader.getSystemClassLoader(), + new Class[]{int.class}, + maxExpectedSize); + } + + void add(Method method); + + void addUnlessDeclaredExists(Method method, Class declaringClass); + + void consolidate(Method method, Class declaringClass); + + int getSize(); + + void clear(); + + Method[] getMethods(); + + Method getFirstMethodWithMostSpecificReturnType(); + } +} --- /dev/null 2014-11-30 10:11:31.639805301 +0100 +++ new/test/lib/testlibrary/jdk/testlibrary/TestProxy.java 2014-11-30 18:51:43.179528124 +0100 @@ -0,0 +1,156 @@ +/* + * Copyright (c) 1994, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.testlibrary; + +import java.lang.reflect.*; +import java.util.HashMap; +import java.util.Map; + +/** + * Factory for proxy that delegates invocations of it's methods + * to methods with same signature on target object. + * Used to invoke otherwise inaccessible constructors and + * instance methods for the purpose of testing. + * + * @author Peter Levart + */ +public class TestProxy { + + /** + * Create a proxy instance of type {@code interfaze} and the underlying + * delegate object of class with name {@code className} loaded by given + * {@code classLoader}. The returned proxy instance delegates all method + * invocations to the underlying delegate object so that a matching + * method is invoked on the delegate. + * + * @param interfaze the interface of proxy to be created + * @param className the class name of the underlying delegate object + * to be created + * @param classLoader the class loader used to load the class of + * delegate + * @param parameterTypes the parameter types of the constructor to invoke + * to create new delegate + * @param initargs the arguments passed to delegate constructor + * @param the generic type of proxy object returned + * @return newly created proxy that delegates invocations to newly created + * delegate object + */ + public static T newInstance(Class interfaze, + String className, ClassLoader classLoader, + Class[] parameterTypes, Object... initargs) { + try { + Class clazz = Class.forName(className, false, classLoader); + Constructor constructor = clazz.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + Object instance = constructor.newInstance(initargs); + @SuppressWarnings("unchecked") + T proxy = (T) Proxy.newProxyInstance(interfaze.getClassLoader(), + new Class[]{interfaze}, + new Handler(interfaze, instance)); + return proxy; + } catch (ClassNotFoundException | NoSuchMethodException | + InvocationTargetException | InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private TestProxy() {} // not to be instantiated + + private static class Handler implements InvocationHandler { + + private final Map methodMap = new HashMap<>(); + private final Object target; + + Handler(Class interfaze, Object target) { + this.target = target; + Class clazz = target.getClass(); + for (Method m : interfaze.getMethods()) { + if (Modifier.isStatic(m.getModifiers())) { + continue; // skip static methods + } + Method tm = findMethod(clazz, + m.getName(), + m.getParameterTypes(), + m.getReturnType()); + if (tm == null) { + throw new NoSuchMethodError("Can't find method with same signature as: " + m + + " on target class: " + clazz.getName()); + } + methodMap.put(m, tm); + } + } + + private static Method findMethod(Class clazz, + String name, + Class[] parameterTypes, + Class returnType) { + try { + Method m = clazz.getDeclaredMethod(name, parameterTypes); + if (!Modifier.isStatic(m.getModifiers())) { + if (m.getReturnType() != returnType) { + throw new NoSuchMethodError("Method: " + m + + " has invalid return type. Expecting: " + + returnType.getName()); + } + m.setAccessible(true); + return m; + } + } catch (NoSuchMethodException e) { + } + + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + Method m = findMethod(superclass, name, parameterTypes, returnType); + if (m != null) { + return m; + } + } + + for (Class interfaze : clazz.getInterfaces()) { + Method m = findMethod(interfaze, name, parameterTypes, returnType); + if (m != null) { + return m; + } + } + + return null; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Method targetMethod = methodMap.get(method); + if (targetMethod == null) { + throw new NoSuchMethodError("No mapping exists for: " + method); + } + try { + return targetMethod.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } +}