Well I feel I'm doing something wrong, but now everything compiles and I get a RuntimeException when running my example :
More or less the same class :
shared class ListenerHelper<Args>() given Args satisfies Anything[] {
variable Array<Callable<Anything, Args>> listeners = array<Callable<Anything, Args>>([]);
shared void addListener(Callable<Anything, Args> listener) {
if (listeners.contains(listener)) {
return;
}
Array<Callable<Anything, Args>> newListeners = arrayOfSize(listeners.size+1, listener);
if (listeners.size > 0) {
listeners.copyTo(newListeners, 0, 0, listeners.size);
}
listeners = newListeners;
}
shared void removeListener(Callable<Anything, Args> listener) {
variable Integer index = 0;
for (i->l in listeners.indexed) {
if (l == listener) {
index = i;
break;
}
}
Array<Callable<Anything, Args>> newListeners = arrayOfSize(listeners.size+1, listener);
if (index > 0) {
listeners.copyTo(newListeners, 0, 0, index);
}
if (index < listeners.size) {
listeners.copyTo(newListeners, index+1, index, listeners.size-index-1);
}
listeners = newListeners;
}
shared void fireEvent(Args args) {
for (listener in listeners) {
listener(args);
}
}
}
And the test :
void testListenerHelper() {
variable Boolean changed = false;
void listener(String prop) {
changed = true;
}
ListenerHelper<[String]> helper = ListenerHelper<[String]>();
helper.addListener(listener);
helper.fireEvent([ "Event" ]);
assert(changed == true);
}
When I run the test, I get this :
Exception in thread "main" java.lang.RuntimeException: java.lang.ClassCastException: ceylon.language.ArraySequence cannot be cast to ceylon.language.String
at com.redhat.ceylon.compiler.java.runtime.ide.Launcher.invokeMain(Launcher.java:86)
at com.redhat.ceylon.compiler.java.runtime.ide.Launcher.main(Launcher.java:53)
Caused by: java.lang.ClassCastException: ceylon.language.ArraySequence cannot be cast to ceylon.language.String
at org.granite.test.databinding.testListenerHelper_$1.$call(TestListenerHelper.ceylon:13)
at org.granite.databinding.ListenerHelper.fireEvent(ListenerHelper.ceylon:38)
at org.granite.test.databinding.testListenerHelper_.testListenerHelper(TestListenerHelper.ceylon:15)
at org.granite.test.databinding.testListenerHelper_.main(TestListenerHelper.ceylon)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
What I understand from this is that fireEvent tries to call the listener with one single argument of type Array, when the listener expects a String.
In fact when seeing this, I don't even know how the runtime would know what to do :
shared void fireEvent(Args args) {
for (listener in listeners) {
listener(args);
}
}
listener is a Callable<Anything, Args>, so I guess it should unpack the sequence or arguments and pass them to the callable.
However that feels wrong to me, why wouldn't it just pass the array as is as a single argument to the method ?
The problem is that everything compiles now and I really don't know if I'm doing something utterly stupid or if there is a bug somewhere.
I've also tried this just in case, but I got exactly the same error :
shared void fireEvent(Args args) {
for (listener in listeners) {
if (args.size == 0) {
listener();
}
else if (args.size == 1) {
listener(args[0]);
}
else if (args.size == 2) {
listener(args[0], args[1]);
}
else if (args.size == 3) {
listener(args[0], args[1], args[2]);
}
}
}