I did some work in the past week and I now have a strong (yet partial) implementation of generics with ProxyGen. At least it suits my needs with the 500+ class library that I converted to proxies. It's still partial support because as you said it is not solving the general case. Instead it provides generics for the major collection types (List, Map, Iterable, Collection and Set).
What a learning curve with ProxyGen -- I never used CodeDom before, but it's very cool. Powerful. My addition to ProxyGen is a big hack :) and I'm sure the stuff I wrote can be done much much neater, more generic or more robust, but again, it suits my needs. I am willing to share the changed/hacked sources... Let me know if interested.
So this is the approach I took (the stuff below is mainly to expose C# generics to Java, but a very similar approach can be done for Java to C# as well):
1. I wrote C# convertor classes that take a generic collection type and return the same elements only inside a non-generic collection type. Example System.Collections.Generic.IList<T> to System.Collection.IList. I made these convertors for each collection type mentioned above. They return the same elements, but inside a different container;
public static IList ToIList<T>(IList<T> source) {
return new ArrayList((List<T>)source);
}
2. Then I changed ProxyGen to wrap the call to the __real object into these convertors and changed the type argument of the FullC2J to the non-generic collection:
@__return = global::net.sf.jni4net.utils.Convertor.FullC2J<global::System.Collections.IList>(@__env, Mitza.Jni4Net.Convertor.ToIList(@__real.ComponentPresentations));
3. On the Java side, modified ProxyGen to change the signature of the native method to use the non-generic collection type:
public native system.collections.IList getComponentPresentations();
4. Added a new delegate (the generic) that actually calls the non-generic native. The idea here is to be able to get the generics directly from the proxy class without me having to wrap them:
public java.util.List<tridion.contentmanager.communicationmanagement.ComponentPresentation> getComponentPresentationsList() {
return new mitza.jni4net.wrapper.List<tridion.contentmanager.communicationmanagement.ComponentPresentation>(getComponentPresentations());
}
5. And of course I wrote the Java wrappers (for each collection type) to 'convert' the non-generic List (as per above) to the generic List<T>. All operations on these collections happen in fact on the wrapped non-generic proxy, so the collection elements are 'live' (modifications will show up immediately on the C# side):
public class List<E extends system.Object> implements java.util.List<E> {
private IList iList;
public boolean add(E element) {
iList.Add(element);
return true;
} //... etc
6. For the Java to C# generic support, I wrote the 'same' wrappers only this time on the C# side and they wrap the java.util.* collection proxy objects:
public class List<T> : System.Collections.Generic.IList<T> where T : java.lang.Object {
private java.util.List list;
public int IndexOf(T item) {
return list.indexOf(item);
}
There are some limitations, as you were already pointing out
- I currently don't create new collections. That might be tricky. Creating new items in a collection it's not a problem
- Primitive wrapper type arguments are not supported for generics in ProxyGen. In this case I simply skip generating the Java delegate
- The wrappers have an erasure to the 'proxy' class. So in Java wrapper you have to use a C# proxy object that inherits from system.Object (the same in C# wrapper, have to use something from under java.lang.Object)
Overall I'm pretty pleased with the setup. It works nicely. But what a nightmare it was with the KeyValuePair<K, V> for types such as List<KeyValurPair<K, V>>. It is very difficult to nail a completely general solution.
I can do Java-style loops like this:
for (ComponentPresentation cp : page.getComponentPresentationsList()) {
Component component = cp.getComponent();
ComponentTemplate componentTemplate = cp.getComponentTemplate();
System.out.println(String.format("\tCP: Component: '%s' %s + Component Template: '%s' %s",
component.getTitle(), component.getId(), componentTemplate.getTitle(), componentTemplate.getId()));
}
Thanks again for creating jni4net and proxygen, they are such cool tools!