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 net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default.WRAPPER;
37 import static net.bytebuddy.implementation.FixedValue.value;
38 import static net.bytebuddy.implementation.MethodCall.invoke;
39 import static net.bytebuddy.implementation.MethodDelegation.to;
40 import static net.bytebuddy.matcher.ElementMatchers.*;
41
42 import java.io.Serializable;
43 import java.lang.reflect.Constructor;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Modifier;
46 import java.util.HashMap;
47 import java.util.Map;
48
49 import org.reflections.ReflectionUtils;
50
51 import lombok.SneakyThrows;
52 import net.bytebuddy.ByteBuddy;
53 import net.bytebuddy.TypeCache;
54 import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
55 import net.bytebuddy.implementation.bind.annotation.Origin;
56 import net.bytebuddy.implementation.bind.annotation.RuntimeType;
57 import net.bytebuddy.implementation.bind.annotation.This;
58
59
60
61
62
63 public final class ViewContextProxy {
64
65 static TypeCache<Class> proxyTypeCache = new TypeCache.WithInlineExpunction<>(TypeCache.Sort.WEAK);
66
67 @SneakyThrows
68 public <T extends ViewContext> T createViewContext(Class<T> clazz) {
69 return createViewContext(clazz, new HashMap<>());
70 }
71
72 @SneakyThrows
73 public <T extends ViewContext> T createViewContext(Class<T> clazz, Map<String, ContextProperty> properties) {
74 if (!clazz.isInterface()) {
75 throw new RuntimeException("Context type has to be an interface");
76 }
77
78 return (T) proxyTypeCache.findOrInsert(Thread.currentThread().getContextClassLoader(), clazz, () -> generateViewContextProxy(clazz)).getConstructor(Map.class).newInstance(properties);
79 }
80
81 private <T extends ViewContext> Class<T> generateViewContextProxy(Class<T> clazz) throws NoSuchMethodException {
82 final Constructor<ViewContextBase> targetCtor = ViewContextBase.class.getConstructor(Map.class);
83 final Class<? extends ViewContextBase> getViewContextType = new ByteBuddy()
84 .subclass(ViewContextBase.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
85 .implement(clazz, Serializable.class)
86 .method(named("getViewContextType")).intercept(value(clazz))
87 .method(returns(ContextProperty.class))
88 .intercept(to(new StatePropertyGetterDelegate()))
89 .defineConstructor(Modifier.PUBLIC)
90 .withParameters(Map.class)
91 .intercept(invoke(targetCtor)
92 .withAllArguments()
93 .andThen(to(new ContextInitializer())))
94 .make()
95 .load(getClass().getClassLoader(), WRAPPER)
96 .getLoaded();
97 return (Class<T>) getViewContextType;
98 }
99
100
101
102
103 public static class ContextInitializer {
104
105 @SuppressWarnings("unchecked")
106 public void init(@This ViewContextBase that) {
107 ReflectionUtils.getMethods(that.getClass()).stream()
108 .filter(method -> ContextProperty.class.isAssignableFrom(method.getReturnType()))
109 .filter(method -> !that.properties().containsKey(method.getName()))
110 .forEach(method-> that.properties().put(method.getName(), new ContextProperty.Impl()));
111 }
112 }
113
114
115
116
117 public static class StatePropertyGetterDelegate {
118
119 @RuntimeType
120 public ContextProperty getter(@Origin Method method, @This ViewContextBase that) {
121 return that.properties().get(method.getName());
122 }
123 }
124 }