diff --git a/platform/util/src/com/intellij/util/JBIterableClassTraverser.java b/platform/util/src/com/intellij/util/JBIterableClassTraverser.java new file mode 100644 index 000000000000..b0a10514535f --- /dev/null +++ b/platform/util/src/com/intellij/util/JBIterableClassTraverser.java @@ -0,0 +1,21 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util; + +import com.intellij.util.containers.JBIterable; +import com.intellij.util.containers.JBTreeTraverser; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Do not use in ReflectionUtil and any such low-level and early (start-up) code. + */ +public final class JBIterableClassTraverser { + @NotNull + public static JBTreeTraverser> classTraverser(@Nullable Class root) { + return CLASS_TRAVERSER.unique().withRoot(root); + } + + private static final JBTreeTraverser> CLASS_TRAVERSER = JBTreeTraverser.from( + (Function, ? extends Iterable>>)aClass -> JBIterable.>of(aClass.getSuperclass()) + .append(aClass.getInterfaces())); +} diff --git a/platform/util/src/com/intellij/util/ReflectionUtil.java b/platform/util/src/com/intellij/util/ReflectionUtil.java index 4bfc19199826..353a95dacb9c 100644 --- a/platform/util/src/com/intellij/util/ReflectionUtil.java +++ b/platform/util/src/com/intellij/util/ReflectionUtil.java @@ -4,9 +4,9 @@ package com.intellij.util; import com.intellij.openapi.diagnostic.ControlFlowException; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.DifferenceFilter; -import com.intellij.util.containers.*; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Predicate; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,7 +15,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.util.*; -public class ReflectionUtil { +public final class ReflectionUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.util.ReflectionUtil"); private ReflectionUtil() { } @@ -123,7 +123,7 @@ public class ReflectionUtil { @NotNull public static List collectFields(@NotNull Class clazz) { List result = new ArrayList<>(); - for (Class c : classTraverser(clazz)) { + for (Class c : JBIterableClassTraverser.classTraverser(clazz)) { ContainerUtil.addAll(result, c.getDeclaredFields()); } return result; @@ -140,16 +140,45 @@ public class ReflectionUtil { @NotNull public static Field findAssignableField(@NotNull Class clazz, @Nullable("null means any type") final Class fieldType, @NotNull final String fieldName) throws NoSuchFieldException { Field result = findFieldInHierarchy(clazz, field -> fieldName.equals(field.getName()) && (fieldType == null || fieldType.isAssignableFrom(field.getType()))); - if (result != null) return result; + if (result != null) { + return result; + } throw new NoSuchFieldException("Class: " + clazz + " fieldName: " + fieldName + " fieldType: " + fieldType); } @Nullable - private static Field findFieldInHierarchy(@NotNull Class clazz, @NotNull Condition checker) { - for (Class c : classTraverser(clazz)) { - Field field = ContainerUtil.find(c.getDeclaredFields(), checker); + private static Field findFieldInHierarchy(@NotNull Class rootClass, @NotNull java.util.function.Predicate checker) { + for (Class aClass = rootClass; aClass != null; aClass = aClass.getSuperclass()) { + for (Field field : aClass.getDeclaredFields()) { + if (checker.test(field)) { + field.setAccessible(true); + return field; + } + } + } + + // ok, let's check interfaces + return processInterfaces(rootClass.getInterfaces(), new HashSet<>(), checker); + } + + @Nullable + private static Field processInterfaces(@NotNull Class[] interfaces, + @NotNull Set> visited, + @NotNull java.util.function.Predicate checker) { + for (Class anInterface : interfaces) { + if (!visited.add(anInterface)) { + continue; + } + + for (Field field : anInterface.getDeclaredFields()) { + if (checker.test(field)) { + field.setAccessible(true); + return field; + } + } + + Field field = processInterfaces(anInterface.getInterfaces(), visited, checker); if (field != null) { - field.setAccessible(true); return field; } } @@ -297,7 +326,7 @@ public class ReflectionUtil { public static T getField(@NotNull Class objectClass, @Nullable Object object, @Nullable("null means any type") Class fieldType, @NotNull @NonNls String fieldName) { try { - final Field field = findAssignableField(objectClass, fieldType, fieldName); + Field field = findAssignableField(objectClass, fieldType, fieldName); //noinspection unchecked return (T)field.get(object); } @@ -617,7 +646,7 @@ public class ReflectionUtil { } - private static class MySecurityManager extends SecurityManager { + private static final class MySecurityManager extends SecurityManager { private static final MySecurityManager INSTANCE = new MySecurityManager(); Class[] getStack() { return getClassContext(); @@ -648,12 +677,4 @@ public class ReflectionUtil { public static boolean isAssignable(@NotNull Class ancestor, @NotNull Class descendant) { return ancestor == descendant || ancestor.isAssignableFrom(descendant); } - - @NotNull - public static JBTreeTraverser> classTraverser(@Nullable Class root) { - return CLASS_TRAVERSER.unique().withRoot(root); - } - - private static final JBTreeTraverser> CLASS_TRAVERSER = JBTreeTraverser.from( - (Function, ? extends Iterable>>)aClass -> JBIterable.>of(aClass.getSuperclass()).append(aClass.getInterfaces())); } \ No newline at end of file diff --git a/xml/dom-impl/src/com/intellij/util/xml/ModelMergerImpl.java b/xml/dom-impl/src/com/intellij/util/xml/ModelMergerImpl.java index f7b8ce780f4e..e79f8de83171 100644 --- a/xml/dom-impl/src/com/intellij/util/xml/ModelMergerImpl.java +++ b/xml/dom-impl/src/com/intellij/util/xml/ModelMergerImpl.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.xml; import com.intellij.openapi.util.Pair; @@ -332,15 +318,11 @@ public final class ModelMergerImpl implements ModelMerger { } @Nullable - private static Method findPrimaryKeyAnnotatedMethod(final Method method, final Class aClass) { - if (method.getReturnType() != void.class && method.getParameterTypes().length == 0) { - for (Method each : new JavaMethodSignature(method).getAllMethods(aClass)) { - if (each.getAnnotation(PrimaryKey.class) != null) { - return each; - } - } + private static Method findPrimaryKeyAnnotatedMethod(@NotNull Method sampleMethod, @NotNull Class aClass) { + if (sampleMethod.getParameterCount() != 0 || sampleMethod.getReturnType() == void.class) { + return null; } - return null; + return JavaMethodSignature.findMethod(sampleMethod, aClass, method -> method.isAnnotationPresent(PrimaryKey.class)); } private List getMergedImplementations(final Method method, diff --git a/xml/dom-openapi/src/com/intellij/util/xml/JavaMethodSignature.java b/xml/dom-openapi/src/com/intellij/util/xml/JavaMethodSignature.java index ae7288819309..c541c4960287 100644 --- a/xml/dom-openapi/src/com/intellij/util/xml/JavaMethodSignature.java +++ b/xml/dom-openapi/src/com/intellij/util/xml/JavaMethodSignature.java @@ -1,44 +1,37 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.xml; import com.intellij.util.ArrayUtil; +import com.intellij.util.JBIterableClassTraverser; import com.intellij.util.ReflectionUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.JBIterable; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; /** * @author peter */ -public class JavaMethodSignature { +public final class JavaMethodSignature { private static final Set OBJECT_METHOD_NAMES = ContainerUtil.map2Set(Object.class.getDeclaredMethods(), Method::getName); private final String myMethodName; - private final Class[] myMethodParameters; + private final Class[] myMethodParameters; - public JavaMethodSignature(final String methodName, final Class... methodParameters) { + public JavaMethodSignature(String methodName, Class... methodParameters) { myMethodName = methodName; myMethodParameters = methodParameters.length == 0 ? ArrayUtil.EMPTY_CLASS_ARRAY : methodParameters; } - public JavaMethodSignature(Method method) { - this(method.getName(), method.getParameterTypes()); + public JavaMethodSignature(@NotNull Method method) { + myMethodName = method.getName(); + myMethodParameters = method.getParameterCount() == 0 ? ArrayUtil.EMPTY_CLASS_ARRAY : method.getParameterTypes(); } public String getMethodName() { @@ -46,7 +39,7 @@ public class JavaMethodSignature { } @Nullable - public final Method findMethod(final Class aClass) { + public final Method findMethod(@NotNull Class aClass) { Method method = getDeclaredMethod(aClass); if (method == null && aClass.isInterface() && OBJECT_METHOD_NAMES.contains(myMethodName)) { method = ReflectionUtil.getDeclaredMethod(Object.class, myMethodName, myMethodParameters); @@ -55,16 +48,19 @@ public class JavaMethodSignature { } @Nullable - private Method getDeclaredMethod(final Class aClass) { - final Method method = ReflectionUtil.getMethod(aClass, myMethodName, myMethodParameters); + private Method getDeclaredMethod(@NotNull Class aClass) { + Method method = ReflectionUtil.getMethod(aClass, myMethodName, myMethodParameters); return method == null ? ReflectionUtil.getDeclaredMethod(aClass, myMethodName, myMethodParameters) : method; } - List getAllMethods(Class startFrom) { + @NotNull + List getAllMethods(@NotNull Class startFrom) { List result = new ArrayList<>(); - for (Class superClass : JBIterable.from(ReflectionUtil.classTraverser(startFrom)).append(Object.class).unique()) { + for (Class superClass : JBIterable.from(JBIterableClassTraverser.classTraverser(startFrom)).append(Object.class).unique()) { for (Method method : superClass.getDeclaredMethods()) { - if (methodMatches(method)) { + if (myMethodName.equals(method.getName()) && + method.getParameterCount() == myMethodParameters.length && + Arrays.equals(method.getParameterTypes(), myMethodParameters)) { result.add(method); } } @@ -72,8 +68,24 @@ public class JavaMethodSignature { return result; } - private boolean methodMatches(Method method) { - return myMethodName.equals(method.getName()) && Arrays.equals(method.getParameterTypes(), myMethodParameters); + @Nullable + static Method findMethod(@NotNull Method sampleMethod, @NotNull Class startFrom, @NotNull Predicate checker) { + String sampleMethodName = sampleMethod.getName(); + Class[] sampleMethodParameters = sampleMethod.getParameterCount() == 0 ? ArrayUtil.EMPTY_CLASS_ARRAY : sampleMethod.getParameterTypes(); + + for (Class superClass : JBIterable.from(JBIterableClassTraverser.classTraverser(startFrom)).append(Object.class).unique()) { + for (Method method : superClass.getDeclaredMethods()) { + if (sampleMethodName.equals(method.getName()) && + method.getParameterCount() == sampleMethodParameters.length && + Arrays.equals(method.getParameterTypes(), sampleMethodParameters)) { + if (checker.test(method)) { + return method; + } + } + } + } + + return null; } public String toString() {