Skip to content

Commit 9b94b69

Browse files
committed
Add java record support
1 parent 7995270 commit 9b94b69

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package de.neuland.pug4j.jexl3;
2+
3+
import de.neuland.pug4j.model.RecordWrapper;
4+
import org.apache.commons.jexl3.JexlEngine;
5+
import org.apache.commons.jexl3.JexlException;
6+
import org.apache.commons.jexl3.internal.introspection.Uberspect;
7+
import org.apache.commons.jexl3.introspection.JexlMethod;
8+
import org.apache.commons.jexl3.introspection.JexlPermissions;
9+
import org.apache.commons.jexl3.introspection.JexlUberspect;
10+
import org.apache.commons.logging.Log;
11+
12+
import java.lang.reflect.Method;
13+
14+
/**
15+
* Custom Uberspect that enables method calls on RecordWrapper by delegating to the underlying record.
16+
*/
17+
public class RecordWrapperUberspect extends Uberspect {
18+
19+
public RecordWrapperUberspect(Log log, JexlUberspect.ResolverStrategy strategy, JexlPermissions permissions) {
20+
super(log, strategy, permissions);
21+
}
22+
23+
@Override
24+
public JexlMethod getMethod(Object obj, String method, Object... args) {
25+
// For RecordWrapper, delegate method resolution to the underlying record
26+
if (obj instanceof RecordWrapper wrapper) {
27+
Object record = wrapper.getRecord();
28+
Class<?> recordClass = record.getClass();
29+
30+
// Try to find the method using Java reflection directly
31+
// Record accessors are no-arg methods with the component name
32+
if (recordClass.isRecord() && (args == null || args.length == 0)) {
33+
try {
34+
Method javaMethod = recordClass.getMethod(method);
35+
// Found a matching method, create a JexlMethod wrapper for it
36+
return new DirectRecordMethod(javaMethod, wrapper);
37+
} catch (NoSuchMethodException e) {
38+
// Method not found, fall through to default handling
39+
}
40+
}
41+
42+
// If not a record or method not found, try standard JEXL resolution
43+
JexlMethod recordMethod = super.getMethod(record, method, args);
44+
if (recordMethod != null) {
45+
return new RecordDelegatingMethod(recordMethod, wrapper);
46+
}
47+
}
48+
49+
// For all other objects, use default behavior
50+
return super.getMethod(obj, method, args);
51+
}
52+
53+
/**
54+
* Direct wrapper around a Java Method for record accessors.
55+
*/
56+
private static class DirectRecordMethod implements JexlMethod {
57+
private final Method method;
58+
private final RecordWrapper wrapper;
59+
60+
DirectRecordMethod(Method method, RecordWrapper wrapper) {
61+
this.method = method;
62+
this.wrapper = wrapper;
63+
method.setAccessible(true);
64+
}
65+
66+
@Override
67+
public Object invoke(Object obj, Object... params) throws Exception {
68+
// Invoke on the underlying record, not the wrapper
69+
Object result = method.invoke(wrapper.getRecord(), params);
70+
// Wrap the result if it's a record
71+
return RecordWrapper.wrapIfRecord(result);
72+
}
73+
74+
@Override
75+
public Object tryInvoke(String name, Object obj, Object... params) {
76+
try {
77+
return invoke(obj, params);
78+
} catch (JexlException.TryFailed xjexl) {
79+
throw xjexl;
80+
} catch (Exception xany) {
81+
return JexlEngine.TRY_FAILED;
82+
}
83+
}
84+
85+
@Override
86+
public boolean tryFailed(Object rval) {
87+
return rval == JexlEngine.TRY_FAILED;
88+
}
89+
90+
@Override
91+
public boolean isCacheable() {
92+
return true;
93+
}
94+
95+
@Override
96+
public Class<?> getReturnType() {
97+
return method.getReturnType();
98+
}
99+
}
100+
101+
/**
102+
* Wraps a JexlMethod to invoke it on the underlying record instead of the RecordWrapper.
103+
*/
104+
private static class RecordDelegatingMethod implements JexlMethod {
105+
private final JexlMethod delegate;
106+
private final RecordWrapper wrapper;
107+
108+
RecordDelegatingMethod(JexlMethod delegate, RecordWrapper wrapper) {
109+
this.delegate = delegate;
110+
this.wrapper = wrapper;
111+
}
112+
113+
@Override
114+
public Object invoke(Object obj, Object... params) throws Exception {
115+
// Invoke on the underlying record, not the wrapper
116+
Object result = delegate.invoke(wrapper.getRecord(), params);
117+
// Wrap the result if it's a record
118+
return RecordWrapper.wrapIfRecord(result);
119+
}
120+
121+
@Override
122+
public Object tryInvoke(String name, Object obj, Object... params) {
123+
try {
124+
return invoke(obj, params);
125+
} catch (JexlException.TryFailed xjexl) {
126+
throw xjexl;
127+
} catch (Exception xany) {
128+
return JexlEngine.TRY_FAILED;
129+
}
130+
}
131+
132+
@Override
133+
public boolean tryFailed(Object rval) {
134+
return rval == JexlEngine.TRY_FAILED;
135+
}
136+
137+
@Override
138+
public boolean isCacheable() {
139+
return delegate.isCacheable();
140+
}
141+
142+
@Override
143+
public Class<?> getReturnType() {
144+
return delegate.getReturnType();
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)