[jdom-interest] JDom and Java5
Victor Toni
victor.toni at ebuconnect.de
Tue Feb 26 06:49:02 PST 2008
Hi Rolf,
Rolf Lear wrote:
> Hi Victor
>
> I have had a look at your suggestion, and I did try to play with similar lines of thought, but they are dead-end paths. Correct me if I am wrong:
>
> Here's your suggestion which I have simplified to get rid of the exception handling.......
>
> public <T> List<T> selectNodes(Object context) {
> currentContext = context;
> return xPath.selectNodes(context);
> currentContext = null;
> }
>
> There are a few issues with this that make it impractical as I see it:
> 1. even if the code were compilable as-is, it would require an explicit cast to work, the return line would actually have to read:
> return (List<T>)xPath.selectNodes(context);
> which introduces 2 problems because firstly, there would be a compile-time warning about unchecked casts, and secondly, there will then be ClassCastExceptions in the client code in the cases where the client's XPath does not match the types represented by <T> (which will happen...!).
>
AFAIK this assumption is not correct, because Generics are more of a
hack than an assertion, an example:
xpath = [ create an XPath for "//@*]
List<Element> elements = xpath.selectNodes(myContext); // no exception
Element element = elements.get(0); // will throw a ClassCastException
IMHO this happens because the generified code would translate to
something like this (internally):
List elements = xpath.selectNodes(myContext); // no exception
Element element = (Element) elements.get(0); // will throw a
ClassCastException
Generics are only to save keystrokes and make written code smaller, when
decompiling the compiled byte code you would have very similar code to
the pre-1.5 era.
> 2. The generic typing you suggest is incomplete. You specify <T> List<T>, but then do not actually use the typing anywhere. Your method signature, to be useful really requires one of two things, either:
> public <T> List<T> selectNodes(Object context, Class<T> contenttype) {
> and then use explicit contentype.cast(.....) calls inside the method to cause valid compiles as well as trap any ClassCastExceptions inside the API rather than the client.
> or
> public List<T> selectNodes(Object context) {
> where the entire XPath class is a generic typed class <T>. Unfortunately this is very hard to accomplish cleanly because much of the work done in XPath is done through static methods, and, Java reflection is used to create the XPath instances that do the work. Because runtime reflection is used, it is very hard to type the data at compile time.
>
Actually the pattern I used is quite common, e.g. as in:
http://java.sun.com/j2se/1.5.0/docs/api/java/util/Collections.html#emptyList()
<T> doesn't need to be stated explicitly because it is specified
implicitly by the left hand side. As mentioned above a
ClassCastException should only occur when trying to access an element
from within the returned list.
If would like to get rid of the casting warning please try a version
similar to this one:
Instead of
public <T> List<T> selectNodes(Object context) {
return xPath.selectNodes(context);
}
you could write:
public <T> List<T> selectNodes(Object context) {
@SuppressWarnings("unchecked")
List<T> typedList = (List<T>) xPath.selectNodes(context);
return typedList;
}
> I don't want to be critical of your suggestion, but, while it is somewhat trivial to consider this problem in isolation, the real complexity of the problem can only be appreciated if you have the full code in front of you and you try it.
>
>
As I have already tried to convert the whole code base myself I know
that even after multiple iterations there are issues left (BTW the
Collections framework has been rewritten three times, and this was done
by the people who "invented" it).
One more example of how this patten could be used. AFAIR you have used
Generics in the Filter interface:
public interface Filter<T extends Content> {
T filter(Content content)
}
Actually this is one of the "not so good" ways to use Generics.
1. You are restricting yourself to be able to filter only Content
instead of applying the restriction in the implementation.
2. You are reusing T in the wrong place
3. You have changed the semantics of Filter, which leads the next point:
4. How do you want to implement NegateFilter?
As you said "it is somewhat trivial to consider this problem in
isolation..."
An alternative approach (which I would prefer) would be:
<S> super class of all objects to be filtered
<R> class of all matching objects
public interface Filter<S, R extends S> {
boolean matches(final S obj);
}
> There is a good chance there is something I have missed so I would be grateful if you could try to flesh out your idea further in the code - how about a fully functional example ;-)
>
> Thanks again.
>
> Rolf
>
Hope this helps,
Victor
>
> On Tue, 26 Feb 2008 13:57:19 +0100, Victor Toni <victor.toni at ebuconnect.de> wrote:
>
>> Hi Rolf,
>>
>> have you tried the approach I mentioned in the previous mail. It doesn't
>> seem to impose changes on the user but gives him flexibility.
>>
>> For convenience the mentioned code snippet:
>>
>> public <T> List<T> selectNodes(Object context) throws JDOMException {
>> try {
>> currentContext = context;
>> return xPath.selectNodes(context);
>> } catch (JaxenException ex1) {
>> throw new JDOMException("XPath error while evaluating \"" +
>> xPath.toString() + "\": " + ex1.getMessage(), ex1);
>> } finally {
>> currentContext = null;
>> }
>> }
>>
>>
>> Kindest regards,
>> Victor
>>
>> Rolf Lear wrote:
>>
>>> Hi All.
>>>
>>> I spent a fair bit of time playing with things over the weekend, and
>>> have decided that making generic versions of the selectNodes process
>>> is somewhat possible if we change the API a lot, but, at some point
>>> there has to be an explicit cast of either the content of a list, or
>>> the list itself. Currently there are no Compiler errors or warnings in
>>> the Java5 JDom, and these changes would require us to tolerate a
>>> compiler warning at some point. As long as we use Jaxen I see no
>>> alternative. Then again, someone may take an approach different to the
>>> ones I have tried, and come up with something that works well.
>>>
>>> The alternatives as I see them are:
>>> a) explicitly casting the returned List from Jaxen to a List<type>,
>>> and then returning the List<type> to the user, at which point, if
>>> there is a mismatch between the content type the user claims is in the
>>> list, and the actual content of the list, then there will be a
>>> ClassCastException when the user reads the List<type>. I see this as
>>> being unacceptable. A modification to this would be to check each
>>> element of the list before returning it to the user at which point the
>>> ClassCastException will happen inside the JDom API and this would be
>>> better, but also add a processing overhead and require a significant
>>> (not recompile-compatible) change to the API method signatures.
>>> b) creating a List<Type> and then checking and casting each element of
>>> the Jaxen List result, and adding these to the List<Type>, but will
>>> add a processing overhead. This will also require
>>> non-recompile-compatible changes to the API.
>>>
>>> For the moment I am giving up on trying to get a clean way to
>>> transform the data in to a more meaningful result. "The Right Thing"
>>> is hard to guarantee without big API changes as far as I can tell.
>>>
>>> It would be great if someone else could give it a go to confirm this.
>>>
>>> For the moment I am leaving the code unchanged to return List<?> until
>>> someone comes up with something better.
>>>
>>> Rolf
>>>
>>> Rolf wrote:
>>>
>>>> Hi Mattias.
>>>>
>>>> Thanks for that. I's made me look further in to things than I had
>>>> before. I put together some test code and I see what you mean about
>>>> casting the results. I have been in the habit of coding using
>>>> eclipse, and I set strict compiler warnings and as a result I see
>>>> lots of errors/warnings generated by eclipse. I always try to resolve
>>>> every issue in my code..... and using raw types always produces
>>>> warnings. This would explain my reluctance for returning just plain
>>>> List. It is a raw type and should be avoided. At the same time, I
>>>> realize now that returning List<Object> has it's drawbacks in that
>>>> you can't cast it to anything directly either... although you can
>>>> actually get away with a double-cast .... List<Element> mylist =
>>>> (List<Element>)(List<?>)(new ArrayList<Object>());
>>>>
>>>> By using plain List it introduces compile-time warnings in to JDom. I
>>>> would prefer to avoid that at all costs. It should be the client side
>>>> that encounters the warnings, not the API side.
>>>>
>>>> I am content with returning List<?> or List<? extends Object> for the
>>>> return type. It indicates that the process has been thought through.
>>>> It would also be real interesting to explore turning the entire XPath
>>>> class in to a generic object.... I'm going to play a little bit and
>>>> get back.
>>>>
>>>> I think there is a fair amount of food for thought. Right now the API
>>>> returns List<?> and this should remove any compile-time issues anyone
>>>> has with the jar, so, for those interested, it is not a hurdle....
>>>>
>>>> Rolf
>>>>
>>>> Mattias Jiderhamn wrote:
>>>>
>>>>> (To be completely honest I have not read the whole post Rolf...)
>>>>>
>>>>> Without having analyzed if this depends on some compiler parameter
>>>>> or other project setting, here is what I experienced.
>>>>>
>>>>> With return type List (as JDOM is today) I could do
>>>>> List<Element> elements = XPath.selectNodes(doc, path)
>>>>> and only get compiler warnings
>>>>>
>>>>> With return type List<?> (Rolfs second version; which I believe are
>>>>> equivalent to List<? extends Object>) I could do
>>>>> List<Element> elements = (List<Element>) XPath.selectNodes(doc, path)
>>>>>
>>>>> With return type List<Object> I cannot cast but have to do
>>>>> List<Element> elements = new ArrayList<Element>();
>>>>> for(Object o : XPath.selectNodes(doc, path))
>>>>> elements.add((Element)o);
>>>>>
>>>>> I think the last option is by far the least appealing (unless, as I
>>>>> said, there is some setting to fix this).
>>>>> In the choice between List and List<?>/List<? extends Object> the
>>>>> plain List has the advantage of making the Java5 port more of a JAR
>>>>> replacement. It does not require modifying the code to add the cast.
>>>>> Also, Gregor points out that List is for "pre-generics legacy code"
>>>>> and certainly, in this case it could be used to indicate that
>>>>> behinds the scenes there is pre-generics legacy code, which in this
>>>>> case is Jaxen.
>>>>>
>>>>> Therefore I personally vote for "List".
>>>>>
>>>>> BUT there is actually a fourth alternative; add the expected output
>>>>> type as a parameter to the method.
>>>>> List<Element> elements = XPath.selectNodes(doc, path, Element.class);
>>>>> List<Attribute> elements = XPath.selectNodes(doc, path,
>>>>> Attribute.class);
>>>>> List<Object> elements = XPath.selectNodes(doc, path, Object.class);
>>>>> // Mix of different types
>>>>> This may be the most beautiful solution, but also the most
>>>>> noticeable change with a new parameter. What about an overload with
>>>>> one legacy version with List and one with the third parameter???
>>>>>
>>>>> /Mattias
>>>>>
>>>>>
>>>>> Rolf wrote (2008-02-23 15:51):
>>>>>
>>>>>> Hi Gregor
>>>>>>
>>>>>> Hmm... actually, I somewhat disagree (in this particular
>>>>>> circumstance). I have been thinking about this a bit more
>>>>>>
>>>>>> Here's the 'current' code behind the method (after I modified it
>>>>>> yesterday)...
>>>>>>
>>>>>> public List<?> selectNodes(Object context) throws JDOMException {
>>>>>> try {
>>>>>> currentContext = context;
>>>>>> return xPath.selectNodes(context);
>>>>>> } catch (JaxenException ex1) {
>>>>>> throw new JDOMException("XPath error while evaluating \"" +
>>>>>> xPath.toString() + "\": " + ex1.getMessage(), ex1);
>>>>>> } finally {
>>>>>> currentContext = null;
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> Here's the signature on the xPath method that is supplied by Jaxen:
>>>>>>
>>>>>> List <http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html>
>>>>>> *selectNodes*(Object
>>>>>> <http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html>
>>>>>> context) throws JaxenException
>>>>>> <http://jaxen.codehaus.org/apidocs/org/jaxen/JaxenException.html>
>>>>>>
>>>>>> Given that XPath can return all sorts of values (not all of them
>>>>>> are JDom Content (like Attribute which is not Content)), then we
>>>>>> have a real problem with this method.
>>>>>>
>>>>>> There is no way I can see (even by modifying Jaxen) to return a
>>>>>> list of any specific type other than Object. It would be real nice
>>>>>> if an xpath that returned just Elements would have a return type of
>>>>>> List<Element>, but that just isn't feasible.
>>>>>>
>>>>>> The next best thing is to return something appropriate, and that
>>>>>> should be the same as what Jaxen would return if it were Generified.
>>>>>>
>>>>>> Thus, the best thing would be to consider things from Jaxen's POV,
>>>>>> and let the 'client' sort it out.
>>>>>>
>>>>>> Since Jaxen does not know anything about the actual content in the
>>>>>> 'context' (it is all delegated to a JDom specific implementation),
>>>>>> and that implementation has to be able to return both Attribute and
>>>>>> Content, the best that Jaxen could do is return List<Object>.
>>>>>>
>>>>>> The Jaxen code would have no option but to look something like:
>>>>>>
>>>>>> List<Object> ret = new XXXList<Object>();
>>>>>> for (..... nodes in context ...) {
>>>>>> if (... node matches the xpath ...) {
>>>>>> ret.add(node);
>>>>>> }
>>>>>> }
>>>>>>
>>>>>>
>>>>>> because it will potentially have to add Attributes, Elements, Text,
>>>>>> etc. (and that's just for JDom, for other API's it will need other
>>>>>> constructs).
>>>>>>
>>>>>> As a consequence, the Jaxen code, if it were Generified, would have
>>>>>> no option but to return List<Object> from the method, just like it
>>>>>> returns Object from the method selectSingleNode();
>>>>>>
>>>>>> Object
>>>>>> <http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html>
>>>>>> *selectSingleNode*(Object
>>>>>> <http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html>
>>>>>> context) throws JaxenException
>>>>>> <http://jaxen.codehaus.org/apidocs/org/jaxen/JaxenException.html>
>>>>>>
>>>>>> In other words, I have come back around the circle and I believe
>>>>>> that the method should return exactly List<Object> rather than
>>>>>> List<?> or List<? extends Object>.
>>>>>>
>>>>>> As further considerations against the alternatives:
>>>>>>
>>>>>> returning List<? extends Object> implies that the actual returned
>>>>>> list may be any other specific type as well, e.g. List<String> or
>>>>>> List<Element> or even something silly like List<System>. This will
>>>>>> not be the case, the actual returned type will in fact be
>>>>>> List<Object> and there is no point in making it more obscure by
>>>>>> saying List<? extends Object> because that actually reduces the
>>>>>> meaning.
>>>>>>
>>>>>> returning List<?> implies that we have no idea what's in the list,
>>>>>> but we do.
>>>>>>
>>>>>> The logical answer appears to be returning List<Object>. This makes
>>>>>> the most sense until Jaxen finds a way for the method
>>>>>> returnSingleNode(Context) to return a type other than Object.
>>>>>> Here's the Jaxen code:
>>>>>>
>>>>>> public Object selectSingleNode(Object node) throws JaxenException
>>>>>>
>> {
>>
>>>>>> List results = selectNodes( node );
>>>>>> if ( results.isEmpty() ) {
>>>>>> return null;
>>>>>> }
>>>>>> return results.get( 0 );
>>>>>> }
>>>>>>
>>>>>> If Jaxen can be generified to have something like:
>>>>>>
>>>>>> public T <T extends Object> selectSingleNode(Object node) throws
>>>>>> JaxenException {
>>>>>> List<T> results = selectNodes( node );
>>>>>> if ( results.isEmpty() ) {
>>>>>> return null;
>>>>>> }
>>>>>> return results.get( 0 );
>>>>>> }
>>>>>>
>>>>>> only then should we consider the alternatives....
>>>>>>
>>>>>> On the other hand, the client-side code calling the XPath method is
>>>>>> going to have to jump through all sorts of instanceof hoops
>>>>>> anyways, and the return type does not really affect that in any way.
>>>>>>
>>>>>> As for Mattias's original claim that List<?> can be cast to
>>>>>> List<Element> if you know the content will be only Elements, then,
>>>>>> I believe that lends a certain weight to List<?>, but, is the same
>>>>>> not true for List<Object>? At some point you will have a
>>>>>> compile-time warning about unchecked types... unless you do
>>>>>> individual casts to Element on the contents of the List anyway.
>>>>>>
>>>>>> Does this make sense? This whole topic is somewhat interesting, and
>>>>>> I do see the value in different implementations. There appears to
>>>>>> be more than one right answer, all with different compromises. At
>>>>>> the moment I am more in the List<Object> camp but am willing to be
>>>>>> convinced otherwise....
>>>>>>
>>>>>> Rolf
>>>>>>
>>>>>> Gregor Zeitlinger wrote:
>>>>>>
>>>>>>> I think List<? extends Object> is more appropriate.
>>>>>>>
>>>>>>> It shows that the code has been generified (as opposed to using a
>>>>>>> plain List).
>>>>>>> It's similiar to List<?>, but shows that Object is indeed the least
>>>>>>> common denomiator.
>>>>>>> _______________________________________________
>>>>>>> To control your jdom-interest membership:
>>>>>>>
>>>>>>>
More information about the jdom-interest
mailing list