visitor = Visitor.create(String.class, Void.class, opt -> { /*empty*/ });
80 | assertThrows(NullPointerException.class, () -> visitor.visit(null, "hello"));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com.github.forax.exotic/VisitorCallSite.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.exotic;
2 |
3 | import static java.lang.invoke.MethodHandles.exactInvoker;
4 | import static java.lang.invoke.MethodHandles.foldArguments;
5 | import static java.lang.invoke.MethodHandles.guardWithTest;
6 | import static java.lang.invoke.MethodType.methodType;
7 |
8 | import java.lang.invoke.MethodHandle;
9 | import java.lang.invoke.MethodHandles;
10 | import java.lang.invoke.MethodHandles.Lookup;
11 | import java.lang.invoke.MethodType;
12 | import java.lang.invoke.MutableCallSite;
13 | import java.util.HashMap;
14 | import java.util.Objects;
15 |
16 | import com.github.forax.exotic.Visitor.Visitlet;
17 |
18 | class VisitorCallSite extends MutableCallSite {
19 | private static final MethodHandle FALLBACK, TYPECHECK, FIND;
20 | static final MethodHandle VISIT;
21 | static {
22 | Lookup lookup = MethodHandles.lookup();
23 | try {
24 | FALLBACK = lookup.findVirtual(VisitorCallSite.class, "fallback", methodType(MethodHandle.class, Object.class));
25 | VISIT = lookup.findVirtual(Visitlet.class, "visit", methodType(Object.class, Visitor.class, Object.class, Object.class));
26 | TYPECHECK = lookup.findStatic(VisitorCallSite.class, "typecheck", methodType(boolean.class, Class.class, Object.class));
27 | FIND = lookup.findStatic(VisitorCallSite.class, "find", methodType(MethodHandle.class, HashMap.class, Object.class));
28 | } catch (NoSuchMethodException | IllegalAccessException e) {
29 | throw new AssertionError(e);
30 | }
31 | }
32 |
33 | private static final int MAX_DEPTH = 8;
34 |
35 | static Visitor
visitor(MethodType methodType, HashMap, MethodHandle> map) {
36 | MethodHandle mh = new VisitorCallSite(methodType, map)
37 | .dynamicInvoker()
38 | .asType(methodType(Object.class, Object.class, Object.class));
39 | return (expr, parameter) -> {
40 | Objects.requireNonNull(expr);
41 | try {
42 | return (R)mh.invokeExact(expr, parameter);
43 | } catch(Throwable t) {
44 | throw Thrower.rethrow(t);
45 | }
46 | };
47 | }
48 |
49 | private final int depth;
50 | private final VisitorCallSite callsite;
51 | private final HashMap, MethodHandle> map;
52 |
53 | private VisitorCallSite(MethodType methodType, HashMap,MethodHandle> map) {
54 | super(methodType);
55 | this.depth = 0;
56 | this.callsite = this;
57 | this.map = map;
58 | setTarget(foldArguments(exactInvoker(methodType), FALLBACK.bindTo(this)));
59 | }
60 |
61 | private VisitorCallSite(MethodType methodType, VisitorCallSite callsite, int depth, HashMap,MethodHandle> map) {
62 | super(methodType);
63 | this.depth = depth;
64 | this.callsite = callsite;
65 | this.map = map;
66 | setTarget(foldArguments(exactInvoker(methodType), FALLBACK.bindTo(this)));
67 | }
68 |
69 | @SuppressWarnings("unused")
70 | private MethodHandle fallback(Object o) {
71 | Class> receiverClass = o.getClass();
72 | MethodHandle target = map.get(receiverClass);
73 | if (target == null) {
74 | throw new IllegalStateException("no visitlet register for type " + receiverClass.getName());
75 | }
76 |
77 | if (depth == MAX_DEPTH) {
78 | callsite.setTarget(foldArguments(exactInvoker(type()), FIND.bindTo(map)));
79 | } else {
80 | MethodHandle guard = guardWithTest(TYPECHECK.bindTo(receiverClass),
81 | target,
82 | new VisitorCallSite(type(), callsite, depth + 1, map).dynamicInvoker());
83 | setTarget(guard);
84 | }
85 |
86 | return target;
87 | }
88 |
89 | @SuppressWarnings("unused")
90 | private static boolean typecheck(Class> receiverClass, Object receiver) {
91 | return receiverClass == receiver.getClass();
92 | }
93 |
94 | @SuppressWarnings("unused")
95 | private static MethodHandle find(HashMap, MethodHandle> map, Object o) {
96 | Class> receiverClass = o.getClass();
97 | MethodHandle target = map.get(receiverClass);
98 | if (target == null) {
99 | throw new IllegalStateException("no visitlet register for type " + receiverClass.getName());
100 | }
101 | return target;
102 | }
103 | }
--------------------------------------------------------------------------------
/src/main/java/com.github.forax.exotic/StringSwitchCallSite.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.exotic;
2 |
3 | import static com.github.forax.exotic.StringSwitch.NO_MATCH;
4 | import static java.lang.invoke.MethodHandles.constant;
5 | import static java.lang.invoke.MethodHandles.dropArguments;
6 | import static java.lang.invoke.MethodHandles.guardWithTest;
7 | import static java.lang.invoke.MethodHandles.insertArguments;
8 | import static java.lang.invoke.MethodType.methodType;
9 |
10 | import java.lang.invoke.MethodHandle;
11 | import java.lang.invoke.MethodHandles;
12 | import java.lang.invoke.MethodHandles.Lookup;
13 | import java.lang.invoke.MethodType;
14 | import java.lang.invoke.MutableCallSite;
15 | import java.util.HashMap;
16 | import java.util.Objects;
17 |
18 | class StringSwitchCallSite extends MutableCallSite {
19 | private static final MethodType STRING_TO_INT = methodType(int.class, String.class);
20 | private static final MethodHandle FALLBACK, EQUALS, GET_OR_DEFAULT, NULLCHECK;
21 | static {
22 | Lookup lookup = MethodHandles.lookup();
23 | try {
24 | FALLBACK = lookup.findVirtual(StringSwitchCallSite.class, "fallback", STRING_TO_INT);
25 | EQUALS = lookup.findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
26 | MethodHandle get = lookup.findVirtual(HashMap.class, "getOrDefault", methodType(Object.class, Object.class, Object.class));
27 | GET_OR_DEFAULT = MethodHandles.insertArguments(get, 2, -1).asType(methodType(int.class, HashMap.class, String.class));
28 | MethodHandle nullCheck = lookup.findStatic(Objects.class, "isNull", methodType(boolean.class, Object.class));
29 | NULLCHECK = nullCheck.asType(methodType(boolean.class, String.class));
30 | } catch(NoSuchMethodException | IllegalAccessException e) {
31 | throw new AssertionError(e);
32 | }
33 | }
34 |
35 | private static final int MAX_DEPTH = 32;
36 |
37 | private final int depth;
38 | private final StringSwitchCallSite callsite;
39 | private final String[] stringcases;
40 | private final HashMap map;
41 |
42 | private StringSwitchCallSite(String[] stringcases, HashMap map) {
43 | super(STRING_TO_INT);
44 | this.depth = 0;
45 | this.callsite = this;
46 | this.stringcases = stringcases;
47 | this.map = map;
48 | setTarget(FALLBACK.bindTo(this));
49 | }
50 |
51 | private StringSwitchCallSite(int depth, StringSwitchCallSite callsite, String[] stringcases, HashMap map) {
52 | super(STRING_TO_INT);
53 | this.depth = depth;
54 | this.callsite = callsite;
55 | this.stringcases = stringcases;
56 | this.map = map;
57 | setTarget(FALLBACK.bindTo(this));
58 | }
59 |
60 | static StringSwitchCallSite create(String[] stringcases) {
61 | HashMap map = new HashMap<>();
62 | for(int i = 0; i < stringcases.length; i++) {
63 | String stringcase = Objects.requireNonNull(stringcases[i]);
64 | if (map.put(stringcase, i) != null) {
65 | throw new IllegalStateException(stringcase + " value appear more than once");
66 | }
67 | }
68 | return new StringSwitchCallSite(stringcases, map);
69 | }
70 |
71 | @SuppressWarnings("unused")
72 | private int fallback(String value) {
73 | Objects.requireNonNull(value);
74 | int index = map.getOrDefault(value, NO_MATCH);
75 |
76 | //System.out.println("depth " + depth);
77 |
78 | if (depth == MAX_DEPTH) {
79 | //System.out.println("reach max depth");
80 | callsite.setTarget(GET_OR_DEFAULT.bindTo(map));
81 | return index;
82 | }
83 |
84 | if (depth == stringcases.length) {
85 | //System.out.println("reach cases length");
86 | callsite.setTarget(createCascadeIfEquals(stringcases));
87 | return index;
88 | }
89 |
90 | setTarget(guardWithTest(insertArguments(EQUALS, 1, value),
91 | dropArguments(constant(int.class, index), 0, String.class),
92 | new StringSwitchCallSite(depth + 1, callsite, stringcases, map).dynamicInvoker()));
93 | return index;
94 | }
95 |
96 | private static MethodHandle createCascadeIfEquals(String[] stringcases) {
97 | MethodHandle target = dropArguments(constant(int.class, NO_MATCH), 0, String.class);
98 | for(int i = stringcases.length; --i >= 0;) {
99 | String stringcase = stringcases[i];
100 | target = guardWithTest(insertArguments(EQUALS, 1, stringcase),
101 | dropArguments(constant(int.class, i), 0, String.class),
102 | target);
103 | }
104 | return target;
105 | }
106 |
107 | static MethodHandle wrapNullIfNecessary(boolean nullMatch, MethodHandle mh) {
108 | if (!nullMatch) {
109 | return mh;
110 | }
111 | return guardWithTest(NULLCHECK,
112 | dropArguments(constant(int.class, StringSwitch.NULL_MATCH), 0, String.class),
113 | mh);
114 | }
115 | }
--------------------------------------------------------------------------------
/src/test/java/com.github.forax.exotic/perf/TypeSwitchBenchMark.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.exotic.perf;
2 |
3 | import java.net.URI;
4 | import java.nio.CharBuffer;
5 | import java.time.LocalDate;
6 | import java.util.Date;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import org.openjdk.jmh.annotations.Benchmark;
10 | import org.openjdk.jmh.annotations.BenchmarkMode;
11 | import org.openjdk.jmh.annotations.Fork;
12 | import org.openjdk.jmh.annotations.Measurement;
13 | import org.openjdk.jmh.annotations.Mode;
14 | import org.openjdk.jmh.annotations.OutputTimeUnit;
15 | import org.openjdk.jmh.annotations.Scope;
16 | import org.openjdk.jmh.annotations.State;
17 | import org.openjdk.jmh.annotations.Warmup;
18 | import org.openjdk.jmh.runner.Runner;
19 | import org.openjdk.jmh.runner.RunnerException;
20 | import org.openjdk.jmh.runner.options.Options;
21 | import org.openjdk.jmh.runner.options.OptionsBuilder;
22 |
23 | import com.github.forax.exotic.TypeSwitch;
24 |
25 | @SuppressWarnings("static-method")
26 | @Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
27 | @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
28 | @Fork(3)
29 | @BenchmarkMode(Mode.AverageTime)
30 | @OutputTimeUnit(TimeUnit.NANOSECONDS)
31 | @State(Scope.Benchmark)
32 | public class TypeSwitchBenchMark {
33 |
34 | interface I { /* empty */ }
35 | interface J { /* empty */ }
36 | static class A { /* empty */ }
37 | static class B implements I { /* empty */ }
38 | static class C implements J { /* empty */ }
39 | static class D implements I, J { /* empty */ }
40 | static class E implements I { /* empty */ }
41 | static class F implements J { /* empty */ }
42 |
43 | private static final TypeSwitch SMALL_TYPE_SWITCH = TypeSwitch.create(true,
44 | D.class, C.class, B.class, A.class/*, J.class, I.class*/);
45 |
46 | private static final TypeSwitch BIG_TYPE_SWITCH = TypeSwitch.create(true,
47 | D.class, C.class, B.class, A.class, J.class, I.class,
48 | String.class, StringBuilder.class, CharSequence.class, URI.class, LocalDate.class, Comparable.class, Object.class);
49 |
50 | private static final Object[] DATA = {
51 | new D(), new E(), new C(), new A(), new F(), new B(), new A(), new E(), new F(), new D() { /*empty*/}, new A() { /*empty*/ },
52 | "hello", new StringBuilder("hello"), CharBuffer.wrap("hello"),
53 | LocalDate.now(), new Date(), A.class
54 | };
55 |
56 | @Benchmark
57 | public int small_small_type_switch() {
58 | int sum = 0;
59 | for(int i = 0; i < 4; i++) {
60 | Object o = DATA[i];
61 | sum += SMALL_TYPE_SWITCH.typeSwitch(o);
62 | }
63 | return sum;
64 | }
65 |
66 | @Benchmark
67 | public int small_small_instanceof_cascade() {
68 | int sum = 0;
69 | for(int i = 0; i < 4; i++) {
70 | Object o = DATA[i];
71 | int value;
72 | if (o == null) { value = TypeSwitch.NULL_MATCH; } else
73 | if (o instanceof D) { value = 0; } else
74 | if (o instanceof C) { value = 1; } else
75 | if (o instanceof B) { value = 2; } else
76 | if (o instanceof A) { value = 3; } else
77 | { value = TypeSwitch.NO_MATCH; }
78 | sum += value;
79 | }
80 | return sum;
81 | }
82 |
83 | @Benchmark
84 | public int small_big_type_switch() {
85 | int sum = 0;
86 | for(Object o: DATA) {
87 | sum += SMALL_TYPE_SWITCH.typeSwitch(o);
88 | }
89 | return sum;
90 | }
91 |
92 | @Benchmark
93 | public int small_big_instanceof_cascade() {
94 | int sum = 0;
95 | for(Object o: DATA) {
96 | int value;
97 | if (o == null) { value = TypeSwitch.NULL_MATCH; } else
98 | if (o instanceof D) { value = 0; } else
99 | if (o instanceof C) { value = 1; } else
100 | if (o instanceof B) { value = 2; } else
101 | if (o instanceof A) { value = 3; } else
102 | { value = TypeSwitch.NO_MATCH; }
103 | sum += value;
104 | }
105 | return sum;
106 | }
107 |
108 | @Benchmark
109 | public int big_big_type_switch() {
110 | int sum = 0;
111 | for(Object o: DATA) {
112 | sum += BIG_TYPE_SWITCH.typeSwitch(o);
113 | }
114 | return sum;
115 | }
116 |
117 | @Benchmark
118 | public int big_big_instanceof_cascade() {
119 | int sum = 0;
120 | for(Object o: DATA) {
121 | int value;
122 | if (o == null) { value = TypeSwitch.NULL_MATCH; } else
123 | if (o instanceof D) { value = 0; } else
124 | if (o instanceof C) { value = 1; } else
125 | if (o instanceof B) { value = 2; } else
126 | if (o instanceof A) { value = 3; } else
127 | if (o instanceof J) { value = 4; } else
128 | if (o instanceof I) { value = 5; } else
129 | if (o instanceof String) { value = 6; } else
130 | if (o instanceof StringBuilder) { value = 7; } else
131 | if (o instanceof CharSequence) { value = 8; } else
132 | if (o instanceof URI) { value = 9; } else
133 | if (o instanceof LocalDate) { value = 10; } else
134 | if (o instanceof Comparable) { value = 11; } else
135 | if (o instanceof Object) { value = 12; } else
136 | { value = TypeSwitch.NO_MATCH; }
137 | sum += value;
138 | }
139 | return sum;
140 | }
141 |
142 | public static void main(String[] args) throws RunnerException {
143 | Options opt = new OptionsBuilder().include(TypeSwitchBenchMark.class.getName()).build();
144 | new Runner(opt).run();
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/test/java/com.github.forax.exotic/StructuralCallTests.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.exotic;
2 |
3 | import static java.lang.invoke.MethodHandles.lookup;
4 | import static java.lang.invoke.MethodHandles.publicLookup;
5 | import static java.lang.invoke.MethodType.methodType;
6 | import static org.junit.jupiter.api.Assertions.assertEquals;
7 | import static org.junit.jupiter.api.Assertions.assertThrows;
8 |
9 | import java.time.LocalTime;
10 | import java.util.List;
11 |
12 | import org.junit.jupiter.api.Test;
13 |
14 | @SuppressWarnings("static-method")
15 | public class StructuralCallTests {
16 | @Test
17 | public void simple() {
18 | StructuralCall call = StructuralCall.create(lookup(), "toString", methodType(String.class));
19 | assertEquals("mirror", call.invoke("mirror"));
20 | assertEquals("14", call.invoke(14));
21 | assertEquals("5.0", call.invoke(5.0));
22 | }
23 |
24 | @Test
25 | public void comparable() {
26 | StructuralCall call =
27 | StructuralCall.create(lookup(), "compareTo", methodType(int.class, Object.class));
28 | assertEquals(0, (int) call.invoke("foo", "foo"));
29 | assertEquals(0, (int) call.invoke(14, 14));
30 | assertEquals(0, (int) call.invoke(LocalTime.of(14, 12), LocalTime.of(14, 12)));
31 | }
32 |
33 | @Test
34 | public void wrongConfiguration() {
35 | assertThrows(
36 | NullPointerException.class,
37 | () -> StructuralCall.create(null, "foo", methodType(void.class)));
38 | assertThrows(
39 | NullPointerException.class,
40 | () -> StructuralCall.create(lookup(), null, methodType(void.class)));
41 | assertThrows(NullPointerException.class, () -> StructuralCall.create(lookup(), "foo", null));
42 | }
43 |
44 |
45 |
46 | @Test
47 | public void cannotAccessToAPrivateMethod() {
48 | StructuralCall call =
49 | StructuralCall.create(lookup(), "m", methodType(String.class, String.class));
50 | assertThrows(IllegalAccessError.class, () -> call.invoke(new com.github.forax.exotic.noaccess.NoAccess(), "test"));
51 | }
52 |
53 | static class WrongLookup {
54 | long m(double d) {
55 | return (long) d;
56 | }
57 | }
58 |
59 | @Test
60 | public void publicLookupCanNotAccessPackageMethod() {
61 | StructuralCall call =
62 | StructuralCall.create(publicLookup(), "m", methodType(long.class, double.class));
63 | assertThrows(IllegalAccessError.class, () -> call.invoke(new WrongLookup(), 4.0));
64 | }
65 |
66 | static class NotFound {
67 | /* empty */
68 | }
69 |
70 | @Test
71 | public void noMethodDefined() {
72 | StructuralCall call =
73 | StructuralCall.create(lookup(), "m", methodType(String.class, String.class));
74 | assertThrows(NoSuchMethodError.class, () -> call.invoke(new NotFound(), "whereAreYou"));
75 | }
76 |
77 | @Test
78 | public void accessMethodThroughInterface() {
79 | StructuralCall call = StructuralCall.create(lookup(), "isEmpty", methodType(boolean.class));
80 | assertEquals(false, (boolean) call.invoke(List.of(1, 2, 3)));
81 | }
82 |
83 | @SuppressWarnings("unused")
84 | static class WrongParameters {
85 | void m(boolean b) {
86 | /* empty */
87 | }
88 |
89 | void m(int i) {
90 | /* empty */
91 | }
92 |
93 | void m(double d) {
94 | /* empty */
95 | }
96 | }
97 |
98 | @Test
99 | public void callingAMethodWithTheWrongClass() {
100 | WrongParameters wrongParameters = new WrongParameters();
101 | StructuralCall call1 =
102 | StructuralCall.create(lookup(), "m", methodType(void.class, boolean.class));
103 | assertThrows(ClassCastException.class, () -> call1.invoke(wrongParameters, "oops"));
104 | StructuralCall call2 = StructuralCall.create(lookup(), "m", methodType(void.class, int.class));
105 | assertThrows(ClassCastException.class, () -> call2.invoke(wrongParameters, "oops"));
106 | StructuralCall call3 =
107 | StructuralCall.create(lookup(), "m", methodType(void.class, double.class));
108 | assertThrows(ClassCastException.class, () -> call3.invoke(wrongParameters, "oops"));
109 | }
110 |
111 | static class WrongNumberOfArguments {
112 | long m(int i, long l) {
113 | return i + l;
114 | }
115 | }
116 |
117 | @Test
118 | public void callingAMethodWithTheWrongNumberOfArguments() {
119 | WrongNumberOfArguments wrongNumberOfArguments = new WrongNumberOfArguments();
120 | StructuralCall call =
121 | StructuralCall.create(
122 | lookup(), "m", methodType(long.class, int.class, long.class)); // 2 parameters
123 | assertThrows(
124 | IllegalArgumentException.class, () -> call.invoke(wrongNumberOfArguments)); // 0 argument
125 | assertThrows(
126 | IllegalArgumentException.class, () -> call.invoke(wrongNumberOfArguments, 0)); // 1 argument
127 | assertThrows(
128 | IllegalArgumentException.class,
129 | () -> call.invoke(wrongNumberOfArguments, 0, 0, 0)); // 3 argument
130 | assertThrows(
131 | IllegalArgumentException.class,
132 | () -> call.invoke(wrongNumberOfArguments, 0, 0, 0, 0)); // 4 argument
133 | assertThrows(
134 | IllegalArgumentException.class,
135 | () -> call.invoke(wrongNumberOfArguments, 0, 0, 0, 0, 0)); // 5 argument
136 | assertThrows(
137 | IllegalArgumentException.class,
138 | () -> call.invoke(wrongNumberOfArguments, 0, 0, 0, 0, 0, 0)); // 6 argument
139 | assertThrows(
140 | IllegalArgumentException.class,
141 | () -> call.invoke(wrongNumberOfArguments, 0, 0, 0, 0, 0, 0, 0)); // 7 argument
142 | assertThrows(
143 | IllegalArgumentException.class,
144 | () -> call.invoke(wrongNumberOfArguments, 0, 0, 0, 0, 0, 0, 0, 0)); // 8 argument
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/java/com.github.forax.exotic/ObjectSupport.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.exotic;
2 |
3 | import java.io.Serializable;
4 | import java.lang.invoke.MethodHandles.Lookup;
5 | import java.lang.reflect.Field;
6 | import java.util.function.Function;
7 |
8 | /**
9 | * Provide a fast implementation for {@link Object#equals(Object)} and {@link Object#hashCode()}.
10 | *
11 | * An {@code ObjectSupport} can be created either from a {@link Lookup}, the class containing the fields
12 | * and a set of lambdas accessing the fields using {@link ObjectSupport#of(Lookup, Class, ProjectionFunction...)}
13 | * or from a {@link Lookup}, the class containing the fields and a set of field names using
14 | * {@link ObjectSupport#of(Lookup, Class, String...)}.
15 | *
16 | * The following example shows how to create and use a {@code ObjectSupport} configured
17 | * to use the fields {@code name} and {@code age}.
18 | *
19 | * class Person {
20 | * private static final ObjectSupport<Person> SUPPORT = SUPPORT = ObjectSupport.of(lookup(), Person.class, p -> p.name, p -> p.age);
21 | *
22 | * private String name;
23 | * private int age;
24 | *
25 | * public Person(String name, int age) {
26 | * this.name = name;
27 | * this.age = age;
28 | * }
29 | *
30 | * public boolean equals(Object other) {
31 | * return SUPPORT.equals(this, other);
32 | * }
33 | *
34 | * public int hashCode() {
35 | * return SUPPORT.hashCode(this);
36 | * }
37 | * }
38 | *
39 | *
40 | * @param the type of the class.
41 | */
42 | public interface ObjectSupport {
43 | /**
44 | * Test if two object are equals.
45 | *
46 | * @param self an instance of the class used to create the current {@code ObjectSupport}.
47 | * @param other any instance or null.
48 | * @return true if the two objects are equals.
49 | * @throws NullPointerException if {@code self} is null.
50 | * @throws ClassCastException if {@code self} is not an instance of the class
51 | * used to create the current {@code ObjectSupport}.
52 | * @see Object#equals(Object)
53 | */
54 | public abstract boolean equals(T self, Object other);
55 |
56 | /**
57 | * Return a hash value of an instance of the class used to create the current {@code ObjectSupport}.
58 | *
59 | * @param self an instance of the class used to create the current {@code ObjectSupport}.
60 | * @return a hash value of {@code self}.
61 | * @throws NullPointerException if {@code self} is null.
62 | * @throws ClassCastException if {@code self} is not an instance of the class
63 | * used to create the current {@code ObjectSupport}.
64 | * @see Object#hashCode()
65 | */
66 | public abstract int hashCode(T self);
67 |
68 | /**
69 | * Return an object support from a lookup object and some field names.
70 | *
71 | * @param the type of the class.
72 | * @param lookup a lookup with enough access rights to see the class fields.
73 | * @param type the class containing the fields.
74 | * @param fieldNames names of the fields that will be use for the computations.
75 | * @return a new fresh object support. This object should always be stored in a {@code static} {@code final} field.
76 | * @throws NullPointerException if {@code lookup} is null, {@code type} is null or the array of field is null.
77 | */
78 | public static ObjectSupport of(Lookup lookup, Class type, String... fieldNames) {
79 | return ObjectSupports.createUsingFieldNames(lookup, type, fieldNames);
80 | }
81 |
82 | /**
83 | * Return an object support from a lookup object and a function that does reflection to find the fields.
84 | *
85 | * @param the type of the class.
86 | * @param lookup a lookup with enough access rights to see the class fields.
87 | * @param type the class containing the fields.
88 | * @param transformer a function that map the class to the fields used for the subsequent computations.
89 | * @return a new fresh object support. This object should always be stored in a {@code static} {@code final} field.
90 | * @throws NullPointerException if {@code lookup} is null, {@code type} is null or {@code transformer} is null.
91 | */
92 | public static ObjectSupport ofReflection(Lookup lookup, Class type, Function super Class, ? extends Field[]> transformer) {
93 | return ObjectSupports.createUsingReflectFields(lookup, type, transformer);
94 | }
95 |
96 | /**
97 | * A function that retrieve the value of a field of the class.
98 | *
99 | * @param type of the parameter.
100 | * @param type of the return value.
101 | *
102 | * @see ObjectSupport#of(Lookup, Class, ProjectionFunction...)
103 | */
104 | @FunctionalInterface
105 | public interface ProjectionFunction extends Function, Serializable {
106 | // empty
107 | }
108 |
109 | /**
110 | * Return an object support from a lookup object and lambdas returning the fields.
111 | *
112 | * @param the type of the class.
113 | * @param lookup a lookup with enough access rights to see the class fields.
114 | * @param type the class containing the fields.
115 | * @param projections lambdas that returns the value of a field of the class.
116 | * @return a new fresh object support. This object should always be stored in a {@code static} {@code final} field.
117 | * @throws NullPointerException if {@code lookup} is null, {@code type} is null or {@code transformer} is null.
118 | * @throws IllegalArgumentException if the code of the lambdas is not accessible from the lookup object or
119 | * if one lambda doesn't do a field access.
120 | */
121 | @SafeVarargs
122 | public static ObjectSupport of(Lookup lookup, Class type, ProjectionFunction super T, ?>... projections) {
123 | return ObjectSupports.createUsingLambdas(lookup, type, projections);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # exotic [](https://github.com/forax/exotic/actions/workflows/main.yml)
2 | A bestiary of classes implementing exotic semantics in Java
3 |
4 | In Java, a static final field is considered as a constant by the virtual machine,
5 | but a final field of an object which is a constant is not itself considered as a constant.
6 | Exotic allows to see a constant's field as a constant, a result of a calculation as a constant,
7 | to change at runtime the value of a constant, etc.
8 |
9 | This library run on Java 8+ and is fully compatible with Java 9 modules.
10 |
11 | This library needs Java 11+ to be built.
12 |
13 | ### MostlyConstant - [javadoc](https://jitpack.io/com/github/forax/exotic/master/javadoc/com/github/forax/exotic/MostlyConstant.html)
14 |
15 | A constant for the VM that can be changed by de-optimizing all the codes that contain the previous value of the constant.
16 |
17 | ```java
18 | private static final MostlyConstant FOO = new MostlyConstant<>(42, int.class);
19 | private static final IntSupplier FOO_GETTER = FOO.intGetter();
20 |
21 | public static int getFoo() {
22 | return FOO_GETTER.getAsInt();
23 | }
24 | public static void setFoo(int value) {
25 | FOO.setAndDeoptimize(value);
26 | }
27 | ```
28 |
29 | ### StableField - [javadoc](https://jitpack.io/com/github/forax/exotic/master/javadoc/com/github/forax/exotic/StableField.html)
30 |
31 | A field that becomes a constant if the object itself is constant and the field is initialized
32 |
33 | ```java
34 | enum Option {
35 | a, b;
36 |
37 | private static final Function