Merged branch 'jetty-10.0.x' into 'jetty-11.0.x'.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2023-07-27 00:09:10 +02:00
commit f415c5ebea
No known key found for this signature in database
GPG Key ID: 1677D141BCF3584D
2 changed files with 206 additions and 17 deletions

View File

@ -119,29 +119,64 @@ public class XmlConfiguration
Class<?> t2 = p2[i].getType();
if (t1 != t2)
{
// Favour derived type over base type
compare = Boolean.compare(t1.isAssignableFrom(t2), t2.isAssignableFrom(t1));
// prefer primitives
compare = Boolean.compare(t2.isPrimitive(), t1.isPrimitive());
if (compare == 0)
{
// favour primitive type over reference
compare = Boolean.compare(!t1.isPrimitive(), !t2.isPrimitive());
if (compare == 0)
// Use name to avoid non determinant sorting
compare = t1.getName().compareTo(t2.getName());
}
// prefer interfaces
compare = Boolean.compare(t2.isInterface(), t1.isInterface());
// break on the first different parameter (should always be true)
if (compare == 0)
{
// prefer most derived
int d1 = calculateDepth(t1);
int d2 = calculateDepth(t2);
compare = Integer.compare(d2, d1);
}
}
}
// break on the first different parameter
if (compare != 0)
break;
}
}
}
compare = Math.min(1, Math.max(compare, -1));
}
return compare;
// failsafe is to compare on the generic string
if (compare == 0)
compare = e1.toGenericString().compareTo(e2.toGenericString());
// Return normalized to -1, 0, 1
return Integer.compare(compare, 0);
};
private static int calculateDepth(Class<?> c)
{
int depth = 0;
if (c.isPrimitive())
return Integer.MIN_VALUE;
if (c.isInterface())
{
Set<Class<?>> interfaces = Set.of(c);
while (!interfaces.isEmpty())
{
depth++;
interfaces = interfaces.stream().flatMap(i -> Arrays.stream(i.getInterfaces())).collect(Collectors.toSet());
}
}
else
{
while (c != Object.class && !c.isPrimitive())
{
depth++;
c = c.getSuperclass();
}
}
return depth;
}
/**
* Set the standard IDs and properties expected in a jetty XML file:
* <ul>
@ -932,9 +967,11 @@ public class XmlConfiguration
throw new IllegalArgumentException("Method name cannot be blank");
// Lets just try all methods for now
Method[] methods = Arrays.stream(oClass.getMethods())
.filter(m -> m.getName().equals(methodName))
.sorted(EXECUTABLE_COMPARATOR)
.toArray(Method[]::new);
Method[] methods = oClass.getMethods();
Arrays.sort(methods, EXECUTABLE_COMPARATOR);
for (Method method : methods)
{
if (!method.getName().equals(methodName))

View File

@ -17,17 +17,22 @@ import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
@ -59,8 +64,10 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jetty.xml.XmlConfiguration.EXECUTABLE_COMPARATOR;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.instanceOf;
@ -728,6 +735,22 @@ public class XmlConfigurationTest
{
}
public void call(Aaa aaa)
{
}
public void call(Bbb aaa)
{
}
public void call(Ccc aaa)
{
}
public void call(Abc abc)
{
}
public void call(Object o)
{
}
@ -745,16 +768,20 @@ public class XmlConfigurationTest
}
}
@RepeatedTest(10)
@RepeatedTest(100)
public void testMethodOrdering() throws Exception
{
List<Method> methods = Arrays.stream(TestOrder.class.getMethods()).filter(m -> "call".equals(m.getName())).collect(Collectors.toList());
Collections.shuffle(methods);
methods.sort(XmlConfiguration.EXECUTABLE_COMPARATOR);
methods.sort(EXECUTABLE_COMPARATOR);
assertThat(methods, Matchers.contains(
TestOrder.class.getMethod("call"),
TestOrder.class.getMethod("call", int.class),
TestOrder.class.getMethod("call", Abc.class),
TestOrder.class.getMethod("call", Aaa.class),
TestOrder.class.getMethod("call", String.class),
TestOrder.class.getMethod("call", Bbb.class),
TestOrder.class.getMethod("call", Ccc.class),
TestOrder.class.getMethod("call", Object.class),
TestOrder.class.getMethod("call", String[].class),
TestOrder.class.getMethod("call", String.class, String[].class)
@ -1927,4 +1954,129 @@ public class XmlConfigurationTest
{
void run() throws Exception;
}
@Test
public void testExecutableComparator() throws Throwable
{
aaa(null);
bbb(null);
ccc(null);
Stream.of(XmlConfigurationTest.class.getMethods())
.filter(m -> m.getName().length() == 3)
.filter(m -> !m.getName().equals("foo"))
.sorted(EXECUTABLE_COMPARATOR)
.map(Executable::toGenericString)
.forEach(System.out::println);
List<Method> methods = Arrays.asList(Arrays.stream(XmlConfigurationTest.class.getMethods())
.filter(m -> m.getName().length() == 3)
.toArray(Method[]::new));
// The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0
assertThat(EXECUTABLE_COMPARATOR.compare(methods.get(0), methods.get(1)), is(EXECUTABLE_COMPARATOR.compare(methods.get(1), methods.get(2))));
assertThat(EXECUTABLE_COMPARATOR.compare(methods.get(0), methods.get(1)), is(EXECUTABLE_COMPARATOR.compare(methods.get(0), methods.get(2))));
}
public void aaa(Aaa ignored)
{
}
public void bbb(Bbb ignored)
{
}
public void ccc(Ccc ignored)
{
}
public interface Aaa
{
}
public interface Abc extends Aaa
{
}
public static class Bbb
{
}
public static class Ccc implements Aaa
{
}
@Test
public void testFooExecutableComparator()
{
List<String> orderedMethodIds = Stream.of(FooObj.class.getMethods())
.filter(m -> m.getName().equals("foo"))
.sorted(EXECUTABLE_COMPARATOR)
.map(Executable::toGenericString)
.collect(Collectors.toList());
orderedMethodIds.forEach(System.out::println);
String[] expectedOrder = {
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo()", // favor fewer args
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(int)", // favor primitives over non-primitives
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(java.time.temporal.Temporal)", // favor over Instant
"public int org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(java.lang.String)",
"public java.util.Locale org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(java.nio.charset.Charset)",
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(java.time.Instant)",
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(java.util.Locale)",
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(int,java.lang.String)",
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(java.lang.String,int)",
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(int,java.lang.String,java.lang.String)",
"public void org.eclipse.jetty.xml.XmlConfigurationTest$FooObj.foo(int,java.lang.String,java.lang.String,java.lang.Object)"
};
assertThat(orderedMethodIds, contains(expectedOrder));
}
public static class FooObj
{
public void foo()
{
}
public int foo(String name)
{
return -1;
}
public void foo(int id)
{
}
public void foo(Locale locale)
{
}
public void foo(Instant timestamp) // Instant extends from Temporal
{
}
public void foo(Temporal temporal)
{
}
public Locale foo(Charset charset)
{
return null;
}
public void foo(String name, int id)
{
}
public void foo(int id, String name)
{
}
public void foo(int id, String name, String description)
{
}
public void foo(int id, String name, String description, Object value)
{
}
}
}