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.dynamic.scaffold.subclass.ConstructorStrategy;
54 import net.bytebuddy.implementation.bind.annotation.Origin;
55 import net.bytebuddy.implementation.bind.annotation.RuntimeType;
56 import net.bytebuddy.implementation.bind.annotation.This;
57
58
59
60
61
62 public final class ViewContextProxy {
63
64 @SneakyThrows
65 public <T extends ViewContext> T createViewContext(Class<T> clazz) {
66 return createViewContext(clazz, new HashMap<>());
67 }
68
69 @SneakyThrows
70 public <T extends ViewContext> T createViewContext(Class<T> clazz, Map<String, ContextProperty> properties) {
71 if (!clazz.isInterface()) {
72 throw new RuntimeException("Context type has to be an interface");
73 }
74
75 return generateViewContextProxy(clazz, properties);
76 }
77
78 private <T extends ViewContext> T generateViewContextProxy(Class<T> clazz, Map<String, ContextProperty> properties) throws InstantiationException, IllegalAccessException, NoSuchMethodException {
79 final Constructor<ViewContextBase> targetCtor = ViewContextBase.class.getConstructor(Map.class);
80 return clazz.cast(new ByteBuddy()
81 .subclass(ViewContextBase.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
82 .implement(clazz, Serializable.class)
83 .method(named("getViewContextType")).intercept(value(clazz))
84 .method(returns(ContextProperty.class))
85 .intercept(to(new StatePropertyGetterDelegate()))
86 .defineConstructor(Modifier.PUBLIC)
87 .intercept(invoke(targetCtor)
88 .with(properties)
89 .andThen(to(new ContextInitializer())))
90 .make()
91 .load(getClass().getClassLoader(), WRAPPER)
92 .getLoaded()
93 .newInstance());
94 }
95
96
97
98
99 public static class ContextInitializer {
100
101 @SuppressWarnings("unchecked")
102 public void init(@This ViewContextBase that) {
103 ReflectionUtils.getMethods(that.getClass()).stream()
104 .filter(method -> ContextProperty.class.isAssignableFrom(method.getReturnType()))
105 .filter(method -> !that.properties().containsKey(method.getName()))
106 .forEach(method -> that.properties().put(method.getName(), new ContextProperty.Impl()));
107 }
108 }
109
110
111
112
113 public static class StatePropertyGetterDelegate {
114
115 @RuntimeType
116 public ContextProperty getter(@Origin Method method, @This ViewContextBase that) {
117 return that.properties().get(method.getName());
118 }
119 }
120 }