Open fluorumlabs opened 4 years ago
Hello. Not sure what do you mean. proxy
does not return values, it generates anonymous classes, similarly to Proxy.newProxyInstance
. You may return whatever you want from one of the generated methods of a proxy, as if it was normal method, e.g.
Metaprogramming.exit(() -> new Object[0]);
The best way to lear metaprogramming deeper is examining sources, for example this one. Also, you can download sources of Flavour and search for usages of metaprogramming API there.
I'm building a proxy around javax.validation
annotation to pass "Annotation" object to the client side. My naive approach was to do the following:
private static <T extends Annotation> Value<T> makeAnnotationProxy(T annotation) {
Class<T> aClass = (Class<T>)annotation.annotationType();
return proxy(aClass, (proxy, method, args) -> {
String name = method.getName();
try {
Object result = aClass.getMethod(name).invoke(annotation);
exit(() -> result);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
});
}
, but that failed on Class<?>[] groups() default { };
method. By looking at the source code, it seems that arrays simply cannot be returned that way. Hence I'm looking for a proper way :)
You can't deal with Class
in metaprogramming API, instead you need ReflectClass
. Please, read documentation. From your code it's unclear what is the entry point for MP and where you get annotation.
Ok, let's generalize a bit. Let's say I have an interface with a method like String[] getSomeStrings()
. How can I implement it via proxy?
Still does not help to answer your question. The best way to implement this interface is to implement it manually, i.e.:
class MyClass implements MyInterface {
@Override String[] getSomeStrings() { return new String[0]; }
}
Futher, if you want to use proxy (I don't know why you need it for just implementing an interface), you might take a look at tests sources. For example, this one:
@Test
public void createsProxy() {
A proxy = createProxy(A.class, "!");
assertEquals("foo!", proxy.foo());
assertEquals("bar!", proxy.bar());
}
@Meta
private static native <T> T createProxy(Class<T> proxyType, String add);
private static <T> void createProxy(ReflectClass<T> proxyType, Value<String> add) {
if (proxyType.getAnnotation(MetaprogrammingClass.class) == null) {
unsupportedCase();
return;
}
Value<T> proxy = proxy(proxyType, (instance, method, args) -> {
String name = method.getName();
exit(() -> name + add.get());
});
exit(() -> proxy.get());
}
Yes, this I know :)
I'm working on javax.validation
(read: hibernate validation) integration for my app. And in order to initialize constraint validator on the client side, I need to provide Annotation instance. I discover those during the compile time, and pass them as a proxies (as, obviously, one cannot subclass annotation). I can ignore Class<?>[]
methods in those annotations, as they are not used by my validation library, but, unfortunately, there is org.hibernate.validator.constraints.Currency
with
/**
* The {@link CurrencyUnit} codes (e.g. USD, EUR...) being accepted.
*/
String[] value();
Of cause, I can make a custom implementation just for that particular validation, but I'd like to have more generalized approach.
Sorry, still don't understand what you are trying to do. What is the client? Where does it get annotation instance? How it's supposed to use these annotation instances?
Client == output of Teavm. Here's a quick and dirty prototype I'm playing with ATM.
...
@Meta
private static native <T> void validate_(Class<T> cls, T instance, String fieldName) throws ValidationException;
private static <T> void validate_(ReflectClass<T> cls, Value<T> instance, Value<String> fieldName) throws ClassNotFoundException {
Map<String, List<Annotation>> annotations = new HashMap<>();
Class<T> tClass = (Class<T>) Metaprogramming.getClassLoader().loadClass(cls.getName());
for (Field field : tClass.getDeclaredFields()) {
List<Annotation> annotationList = new ArrayList<>();
for (Annotation annotation : field.getAnnotations()) {
collectAnnotations(annotation, annotationList::add);
}
if (annotationList.stream().anyMatch(a -> a.annotationType() == Constraint.class)) {
Collections.reverse(annotationList);
annotations.put(field.getName(), annotationList);
}
}
for (ReflectField field : cls.getDeclaredFields()) {
String currentFieldName = field.getName();
List<Annotation> annotationList = annotations.get(field.getName());
if (annotationList != null) {
Value<Boolean> shouldEmit = emit(() -> fieldName.get() == null || currentFieldName.equals(fieldName.get()));
for (Annotation annotation : annotationList) {
emitValidator(cls, field, emit(() -> field.get(instance.get())), shouldEmit, annotation);
}
}
}
}
private static <T> void emitValidator(ReflectClass<T> cls, ReflectField field, Value<Object> o, Value<Boolean> shouldEmit, Annotation annotation) {
tryAnnotation(annotation, Min.class, min -> {
emitValidator(min, MinValidatorForNumber.class, o);
});
}
private static <T, A extends Annotation> void emitValidator(A annotation, Class<?> validatorClass, Value<T> o) {
Value<A> annotationProxy = makeAnnotationProxy(annotation);
ReflectClass<?> validatorReflectClass = findClass(validatorClass);
ReflectMethod method = validatorReflectClass.getMethod("<init>");
MessageInterpolator defaultMessageInterpolator = javax.validation.Validation.byDefaultProvider().configure().getDefaultMessageInterpolator();
InternalContext internalContext = new InternalContext(annotation, (Class<? extends ConstraintValidator<A, ?>>) validatorClass);
String message = defaultMessageInterpolator.interpolate(internalContext.getConstraintDescriptor().getMessageTemplate(), internalContext);
emit(() -> {
ConstraintValidator<A, T> validator = (ConstraintValidator<A, T>) method.construct();
validator.initialize(annotationProxy.get());
if (!validator.isValid((T) o.get(), null)) {
throw new ValidationException(message);
}
});
}
private static <T extends Annotation> Value<T> makeAnnotationProxy(T annotation) {
Class<T> aClass = (Class<T>) annotation.annotationType();
return proxy(aClass, (proxy, method, args) -> {
String name = method.getName();
try {
if (method.getReturnType().isArray() && method.getReturnType().getComponentType().isAssignableFrom(Class.class)) {
exit(() -> null);
// Class<?>[] methods are not used
} else {
Object result = aClass.getMethod(name).invoke(annotation);
exit(() -> result);
// This will probably fail for String[] return type
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
});
}
private static <T extends Annotation> void tryAnnotation(Annotation a, Class<T> target, Consumer<T> emitter) {
if (a.annotationType() == target) {
emitter.accept((T) a);
}
}
...
Ok, I see. The error is here:
Object result = aClass.getMethod(name).invoke(annotation);
exit(() -> result);
This code is not expected to work properly, since you generate value at compile-time and try to propagate it into run time. TeaVM only supports this for primitives and strings. You better to change approach to the way you try to implement validation. For example, you can write specific code for each annotation, much like it's implemented in JSON binding support in Flavour.
TeaVM only supports this for primitives and strings
And this is fine, as one can't really have normal objects in annotatins. Of cause, enums are missing in the list, and this is ok, it should be relatively straightforward to implement. However, arrays of primitives/string are completely missing, meaning that it's impossible to make a proxy for a method returning String[]
or int[]
.
And... Even though it's not expected to work properly, it works for primitives and Strings :)
What is the proper way of returning
Object[]
fromMetaprogramming.proxy()
?