1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework;
35
36 import static java.util.stream.Collectors.joining;
37 import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default.WRAPPER;
38 import static net.bytebuddy.implementation.FixedValue.value;
39 import static net.bytebuddy.implementation.MethodCall.invoke;
40 import static net.bytebuddy.implementation.MethodDelegation.to;
41 import static net.bytebuddy.matcher.ElementMatchers.*;
42
43 import java.io.Serializable;
44 import java.lang.reflect.Constructor;
45 import java.lang.reflect.Method;
46 import java.lang.reflect.Modifier;
47 import java.util.ArrayList;
48 import java.util.HashMap;
49 import java.util.List;
50 import java.util.Map;
51
52 import org.reflections.ReflectionUtils;
53
54 import lombok.SneakyThrows;
55 import net.bytebuddy.ByteBuddy;
56 import net.bytebuddy.TypeCache;
57 import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
58 import net.bytebuddy.implementation.bind.annotation.Origin;
59 import net.bytebuddy.implementation.bind.annotation.RuntimeType;
60 import net.bytebuddy.implementation.bind.annotation.This;
61
62
63
64
65
66 public final class UiComponentContextProxy {
67
68 static TypeCache<Class> proxyTypeCache = new TypeCache.WithInlineExpunction<>(TypeCache.Sort.WEAK);
69
70 @SneakyThrows
71 public <T extends UiComponentContext> T createUiComponentContext(Class<T> clazz) {
72 return createUiComponentContext(clazz, new HashMap<>());
73 }
74
75 @SneakyThrows
76 public <T extends UiComponentContext> T createUiComponentContext(Class<T> clazz, Map<String, ContextProperty> properties) {
77 if (!clazz.isInterface()) {
78 throw new RuntimeException("Context type has to be an interface");
79 }
80
81 return (T) proxyTypeCache.findOrInsert(Thread.currentThread().getContextClassLoader(), clazz, () -> generateUiComponentContextProxy(clazz)).getConstructor(Map.class).newInstance(properties);
82 }
83 private <T extends UiComponentContext> Class<T> generateUiComponentContextProxy(Class<T> clazz) throws NoSuchMethodException {
84
85 final Constructor<UiComponentContextBase> targetCtor = UiComponentContextBase.class.getConstructor(Map.class);
86 final Class<? extends UiComponentContextBase> uiComponentContextType = new ByteBuddy()
87 .subclass(UiComponentContextBase.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
88 .implement(clazz, Serializable.class)
89 .method(named("getUiComponentContextType")).intercept(value(clazz))
90 .method(returns(ContextProperty.class))
91 .intercept(to(new StatePropertyGetterDelegate()))
92 .method(isToString())
93 .intercept(to(new DescribeContext()))
94 .defineConstructor(Modifier.PUBLIC)
95 .withParameters(Map.class)
96 .intercept(invoke(targetCtor)
97 .withAllArguments()
98 .andThen(to(new ContextInitializer())))
99 .make()
100 .load(getClass().getClassLoader(), WRAPPER)
101 .getLoaded();
102 return (Class<T>) uiComponentContextType;
103 }
104
105
106
107
108 public static class ContextInitializer {
109
110 @SuppressWarnings("unchecked")
111 public void init(@This UiComponentContextBase that) {
112 ReflectionUtils.getMethods(that.getClass()).stream()
113 .filter(method -> ContextProperty.class.isAssignableFrom(method.getReturnType()))
114 .filter(method -> !that.properties().containsKey(method.getName()))
115 .forEach(method-> that.properties().put(method.getName(), new ContextProperty.Impl()));
116 }
117 }
118
119
120
121
122 public static class StatePropertyGetterDelegate {
123
124 @RuntimeType
125 public ContextProperty getter(@Origin Method method, @This UiComponentContextBase that) {
126 return that.properties().get(method.getName());
127 }
128 }
129
130 public static class DescribeContext {
131
132 @RuntimeType
133 public String getter(@This UiComponentContextBase that) {
134 List<String> properties = new ArrayList<>();
135 that.properties().forEach((id, property) -> properties.add(String.format("%s -> %s", id, property.nullableValue())));
136 return properties.stream().collect(joining(
137 ", ",
138 that.getUiComponentContextType().getSimpleName() + " [",
139 "]"));
140 }
141 }
142 }