diff options
author | Mickael Istria | 2021-03-31 02:51:52 +0000 |
---|---|---|
committer | Mickael Istria | 2021-03-31 16:10:57 +0000 |
commit | 99074fd4e1982008888507196bb79d7c0c71eb60 (patch) | |
tree | 5e7ed3b11611fea43f308c96fbab345f510b625f | |
parent | ef53f46eaa75fa0899938d2969f83b0904d83970 (diff) | |
download | rt.equinox.p2-99074fd4e1982008888507196bb79d7c0c71eb60.tar.gz rt.equinox.p2-99074fd4e1982008888507196bb79d7c0c71eb60.tar.xz rt.equinox.p2-99074fd4e1982008888507196bb79d7c0c71eb60.zip |
Fix p2ql usage of reflection in Java 16I20210403-0600I20210402-1800I20210402-0510I20210331-1800
p2ql does use reflection for some queries. Java 16 added restrictions on
accessibility, such as non-public method from Java API modules can
strictly not be invoked any more.
So instead of blindly calling the first method match, we crawl the type
hierarchy for a method that match and is accessible (possibly from a
super type or interface).
One example is LinkedHashMap$Entry#getValue() now becoming not
accessible at all in Java 16; we then navigate type hierarchy to resolve
to Map$Entry#getValue(), which is accessible API.
Change-Id: Idd58a4e7a64faf7955b81aaa1c2ce342a2297c8d
Signed-off-by: Mickael Istria <mistria@redhat.com>
-rw-r--r-- | bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java | 97 |
1 files changed, 56 insertions, 41 deletions
diff --git a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java index 5dd3bfd0f..36d09904c 100644 --- a/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java +++ b/bundles/org.eclipse.equinox.p2.metadata/src/org/eclipse/equinox/internal/p2/metadata/expression/Member.java @@ -15,10 +15,14 @@ package org.eclipse.equinox.internal.p2.metadata.expression; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; import java.util.Map; +import java.util.Optional; +import java.util.Queue; import org.eclipse.equinox.p2.metadata.expression.IEvaluationContext; import org.eclipse.equinox.p2.metadata.expression.IExpressionVisitor; import org.eclipse.equinox.p2.metadata.expression.IMemberProvider; @@ -38,10 +42,7 @@ public abstract class Member extends Unary { private static final String GET_PREFIX = "get"; //$NON-NLS-1$ private static final String IS_PREFIX = "is"; //$NON-NLS-1$ - private Class<?> lastClass; - - private transient Method method; - private transient String methodName; + private transient Method lastMethod; DynamicMember(Expression operand, String name) { super(operand, name, Expression.emptyArray); @@ -59,42 +60,11 @@ public abstract class Member extends Unary { if (self == null) throw new IllegalArgumentException("Cannot access member \'" + name + "\' in null"); //$NON-NLS-1$//$NON-NLS-2$ - Class<?> c = self.getClass(); synchronized (this) { - if (methodName == null) { - String n = name; - if (!(n.startsWith(GET_PREFIX) || n.startsWith(IS_PREFIX))) - n = GET_PREFIX + Character.toUpperCase(n.charAt(0)) + n.substring(1); - methodName = n; - } - if (lastClass == null || !lastClass.isAssignableFrom(c)) { - Method m; - for (;;) { - try { - m = c.getMethod(methodName); - if (!Modifier.isPublic(m.getModifiers())) - throw new NoSuchMethodException(); - break; - } catch (NoSuchMethodException e) { - if (methodName.startsWith(GET_PREFIX)) - // Switch from using getXxx() to isXxx() - methodName = IS_PREFIX + Character.toUpperCase(name.charAt(0)) + name.substring(1); - else if (methodName.startsWith(IS_PREFIX)) - // Switch from using isXxx() to xxx() - methodName = name; - else - throw new IllegalArgumentException("Cannot find a public member \'" + name + "\' in a " //$NON-NLS-1$//$NON-NLS-2$ - + self.getClass().getName()); - } - } - - // Since we already checked that it's public. This will speed - // up the calls a bit. - m.setAccessible(true); - lastClass = c; - method = m; - } - + final Method method = (lastMethod != null && lastMethod.getDeclaringClass().isInstance(self)) + ? lastMethod + : findAccessibleMethod(self, name); + this.lastMethod = method; Exception checked; try { return method.invoke(self); @@ -110,10 +80,55 @@ public abstract class Member extends Unary { throw (Error) cause; checked = (Exception) cause; } - throw new RuntimeException("Problem invoking " + methodName + " on a " + self.getClass().getName(), //$NON-NLS-1$ //$NON-NLS-2$ + throw new RuntimeException( + "Problem invoking " + method.getName() + " on a " + self.getClass().getName(), //$NON-NLS-1$ //$NON-NLS-2$ checked); } } + + private Collection<String> getMethodNames(String propertyName) { + Collection<String> res = new ArrayList<>(3); + String n = propertyName; + res.add(propertyName); // obj.value() + if (!(n.startsWith(GET_PREFIX) || n.startsWith(IS_PREFIX))) { + res.add(GET_PREFIX + Character.toUpperCase(n.charAt(0)) + n.substring(1)); // obj.getValue() + res.add(IS_PREFIX + Character.toUpperCase(n.charAt(0)) + n.substring(1)); // obj.isValue() + } + return res; + } + + private Method findAccessibleMethod(Object self, String propertyName) { + Collection<String> methodNamesToTry = getMethodNames(propertyName); + Queue<Class<?>> typesToTry = new LinkedList<>(); + typesToTry.add(self.getClass()); + while (!typesToTry.isEmpty()) { + Class<?> currentClass = typesToTry.poll(); + for (String methodName : methodNamesToTry) { + try { + Method m = currentClass.getMethod(methodName); + if (!m.canAccess(self)) { + try { + // force accessible if possible. + // this seems necessary when invoking objects from + // nested class and "downstream" bundles + m.setAccessible(true); + } catch (Exception e) { + // ignore possible non-blocking case + } + } + if (m.canAccess(self)) { + return m; + } + } catch (NoSuchMethodException e) { + // ignore not found method + } + } + Optional.ofNullable(currentClass.getSuperclass()).ifPresent(typesToTry::add); + typesToTry.addAll(Arrays.asList(currentClass.getInterfaces())); + } + throw new IllegalArgumentException("Cannot find accessor method for property \'" + name + "\' in a " //$NON-NLS-1$//$NON-NLS-2$ + + self.getClass().getName()); + } } public static class LengthMember extends Member { |