The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov's Blog

J2SE Archives


Reading Swing code and learning Java

Posted by kirillcool on July 20, 2006 at 06:14 PM | Permalink | Comments (7)

Browsing the code for BasicScrollBarUI (yes, i don't have a life), i was puzzled by the following lines:
  (ltr ? decrButton : incrButton).setBounds(leftButtonX, 
     itemY, leftButtonW, itemH);
  (ltr ? incrButton : decrButton).setBounds(rightButtonX, 
     itemY, rightButtonW, itemH);
Say what? I had no idea that the ternary conditional operator can appear as a primary expression (JLS 15.8) followed by the method invocation. Well - it can (according to the 15.25 of JLS). Finally I have something to impress my wife. "So, honey, how was your day?", "You wouldn't believe!!! Did you know that the ternary conditional operator can appear as a primary expr... Hey, don't fall asleep!!!"

Native XML support in Dolphin

Posted by kirillcool on July 16, 2005 at 01:10 AM | Permalink | Comments (18)

In "Evolving the Java language" technical session during the last JavaOne, Mark Reinhold shed some light on the future of Java XML support. The slides for the technical sessions have been finally uploaded to conference webpage, so download the PDF for session TS-7955. The executive summary of the relavant slides is:
  • DOM is excellent feature-wise, but requires too much code to be written, and reads poorly.
  • JDOM is more concise, and if you tilt your head and squint, it looks almost like XML.
  • JAXB 2.0 is schema-driven and can not handle "free" XMLs (w/o schema definition) or "intermediate" XMLs (in the middle of the processing).
  • It would be great to directly write XML, but that will drive compiler guys insane.
  • One of possibilities - using hash mark # to refer to XML attributes or subelements.
  • This can be either to get or to set a new value.
  • Enhanced for loop should allow looping over all instances of some attribute.
  • A lot of generified suggestions for new classes (XML and such).

And now, hoping the above list did not offend too many lawyers, let's proceed. As outlined in the first three items, the current state of affairs in working with XML is far from satisfactory. If you take a web designer, it usually means exceptional HTML and CSS skills, and good Javascript. Javascript is very far from Java, but it's fairly easy to use. How about throwing in an XML parser? We can be all excited with StAX, but the "ease of use" column is somewhat misleading. It's easy to use when you talk with your dog in JVM bytecodes, but it's certainly not easier for non-technical guys.

IBM continues to develop XJ - XML enhancements for Java. XJ program treats XSD schemas as "first-class" citizens, allowing to import them as regular Java classes, read and write attributes and elements, unmarshal strings, streams and files to "virtual" objects and marshal these objects back ("virtual" object is object of class that directly corresponds to some schema artifact, without the need for explicitly generating this class). As Mark pointed out during his talk, this approach is too restrictive - you work only on XML that are valid according to a predefined set of schemas.

The same can be said about JAXB 2.0. It doesn't matter if you start with a schema and create classes, or if you start with your classes. As long as the input can not be completely mapped to your classes, the unmarshaller will fail. In addition, you can not add arbitrary elements during the marshalling.

The approach that Mark outlined in his talk is the complete opposite - no schema, no class for the data, only working with XML tags (that in the proposed syntax can not even be externalized). Complete freedom that comes at cost of optional validation, typechecking and syntax that is far from readable (except for your JVM-compliant dog).

So, what am I looking for? Suppose I have two simple classes, Customer and Order, that look like this:
class Order {
  // has get-set pair
  private int id;
}

class Customer {
  // has get-set pair
  private int id;
  // has get-set pair
  private String name;
  // has get-set pair
  private List orders;
}
Simple annotation with JAXB 2.0 can be putting the following on each class:
@XmlAccessorType(AccessType.FIELD)
And maybe the following on Customer:
@XmlRootElement(name = "customer")
Taking a simple XML
<customer>
  <id>1</id>
  <name>Dan</name>
  <order>
    <id>1</id>
  </order>
  <order>
    <id>1</id>
  </order>
</customer>
I'd like to be able to simply write
String xml = ...;  // contains the above XML
Customer cust = xml;
With the auto-unboxing calling JAXB 2.0 unmarshaller (which is already a part of Mustang). The same auto-unboxing should be provided for File, Reader and InputStream as well. If I want to change my customer, i simply change the field:
cust.setName("Arnold");
The marshalling should be as simple as unmarshalling
String newXml = cust;
Here, the auto-boxing should be called. In this case, toString() default behaviour can be the marshalling using JAXB 2.0 (in case Customer class does not override the default implementation of toString()). Auto-boxing should be also provided for Writer, OutputStream and File.

Looping over elements in Customer should be kept as simple as possible:
for (Order order : cust.getOrders[id>3]) {
  System.out.println(order.getId());
}
Here, the compiler knows the exact type of id field and can invoke the getter function. The amount of extra syntax elements (hash mark, slashes, apostrophes) should be 0. The code should be easy to read.

Now, the more interesting problem. What if we don't have schema? What if we are working on XML that has extra elements or attributes that our functions should simply ignore? What if we need to add extra elements or attributes that our functions wish to add for subsequent modules? The answer is simple, and was introduced long ago in Java, and reinforced in 5.0 with generics - extends keyword. Combined with "implicit" properties and functions introduced on enums in 5.0, clean solutions can be provided to the problems stated above.

Suppose that I get the following XML
<customer>
  <id>1</id>
  <name>Dan</name>
  <age>32</age>
  <order>
    <id>1</id>
    <extId>1000</extId>
  </order>
  <order>
    <id>1</id>
    <extId>2000</extId>
  </order>
</customer>
Marked in red - elements that can not be mapped to Customer and Order classes. However, our code doesn't use these elements at all. How can we make our code work and the compiler happy? Make the compiler perform implicit narrowing conversion:
String xml = ...;  // contains the above XML
Customer cust = xml;
The code is exactly as it was. The marshaller should simply discard all the "irrelevant" information, just as done with regular upcast. Of course, the regular upcast doesn't really change the class, so that you can always downcast back (at your own risk). This will be clarified in the following examples.

Suppose now that you wish to handle the new fields, but you can't change the Customer and Order classes (for example, they are part of external jar). Now the code can look like this:
String xml = ...;  // contains the above XML
extends Customer cust = xml;
The extends keyword instructs the compiler (and the unmarshaller) to keep extra information (exactly as done with enums and name() function). This keyword applies to all internal elements (Order in our case). How can we access the new (undeclared) fields - the same way as regular fields:
System.out.println(cust.age);
Here, the unmarshaller stored the value of age in some internal map, and the compiler retrieves that value for us. Here, there are three possible cases:
  • Single instance with simple value
  • Single instance with complex value (inner elements)
  • Multiple instances
In the first case, the value class will be String, in the second - extends Object and in the third - List<extends Object>. The compiler should allow looping over elements even if they are single entries:

// must extend Object as we don't know it's type
for (extends Object age : cust.age) {
  // implicit function provided by the compiler
  if (age.isSimple()) {
    // the cast will succeed
    System.out.println((String)age);
  }
}
What about adding new elements? Simply call
cust.weight = 180;
This can only compile on extends Customer. If the type of "cust" is Customer, the compiler should issue an error message.

Continuing this line of thought, the regular rules for casting, narrowing and passing objects as parameters apply:
private Customer cust1;
private extends Customer cust2;

void test() {
  // narrowing implicit cast - all undeclared
  // attributes and elements are discarded
  cust1 = cust2;
  // widening implicit cast
  cust2 = cust1;
}
Here we have special case - although both cust1 and cust2 point to the same "base" object, changes to cust1 are seen in cust2, but changes in cust2 are seen in cust1 only on declared fields. If we have another extends Customer cust3 that points to cust2, it's the same object. In this case, all three are poiting to the same object in memory, but calling marshaller on cust1 will emit only declared fields, while calling marshaller on cust2 or cust3 will emit all fields. This way, the referencing model is preserved, and the compiler does not allow adding or retrieving undeclared fields from cust1.

Another example -
Set customers = new HashSet();
Customer cust1 = ...;
extends Customer cust2 = ...;
// implicit widening cast
customers.add(cust1);
// regular insert
customers.add(cust2);
for (Customer cust : customers) {
  System.out.println(cust.age);
}
In this case, this function will print null on the cust1.age - it was implicitly widened based on the type of the Set, but doesn't contain information on age.

Another example:
void foo(Customer cust) {
  // widening cast - undeclared fields
  // can appear after the call.
  bar(cust);
}
void bar(extends Customer cust) {
  // implicit narrowing cast - only declared fields
  // can be changed.
  foo(cust);
}
The extended type may provide access to its elements, as enum does (with implicit functions):
Map getAllElements();
where each entry can be either String or List for collections.

The last example is a recursive function that traverses the input XML and dumps its contents to the console. Arguably, this function is not much simpler than its counterpart for DOM. However, most of its logic is both straightforward and simple. In bold red font - the functions that are generated implicitly by the compiler:
void dumpXml(extends Object xmlObj) {
  // see if it is a simple (and implicitly single) element
  if (xmlObj.isSimple()) {
    System.out.println((String)xmlObj);
    return;
  }
  // see if it is a single (and complex because of the previous
  // check) element
  if (xmlObj.isSingle()) {
    // iterate over inner elements. The getElements()
    // function is generated implicitly in the same way as 
    // name() is for enums
    for (Map.Entry element : xmlObj.getElements()) {
      System.out.println(element.getKey()); // element tag name
      dumpXml(element.getValue());          // recurse on value
    }
    return;
  }
  
  // here - we have multiple instances of element with the same
  // tag (collection). Can call .isMultiple() function on 
  // xmlObj
  for (extends Object child : xmlObj.getElements().valueSet()) {
    dumpXml(child);             // recurse on the current child
  }
}


Answers to selected comments

The marshalling and unmarshalling exceptions (including I/O and XML format) should be declared as unchecked. Few new exception classes (may be even one) should "envelop" the existing exceptions (too many of them already). If your code wishes to provide corresponding support - you will have to catch the new exceptions and deal with them correspondingly.

The examples are based on attributes rather than accessors, but using JAXB 2.0 this remains purely a choice. You can go either way (and don't forget that XML attributes and elements do imply straightforward field implementation). For undeclared elements (such as age in the examples above), there can be only a public attribute-style access - after all, they are not declared on the corresponding data class.

XJ's support of undeclared attributes requires the same approach as working with DOM and XPath. The only option to parse XML that is not valid according to some schema is to use XMLElement class, which brings back the "ease-of-use" of DOM. The only example I could find of getting information out of XMLElement was using XPath query (in SequenceInstanceOf.xj). All other examples (which are very scarce) use this class to wrap something inside some tag and either output it as XML or put it inside another tag. There are two problems with this approach:
  • Partial processing of XML can not use schema-derived types. If i know that the input XML contains fields that i wish to ignore, i just don't define them in my data model and work on extends object. In XJ, I'll have to work on XMLElement even if I wish to ignore some fields (unless i have schema definition for every intermediate XML state in my business process chain).
  • XMLElement is not a run-time class as far as I can see from the bundle distribution (the documentation is very scarce). The only way to get elements from it is using XPath, even for simple attributes.
Regarding Xen type-system (thanks for the link). It appears that the authors are trying to mold existing language into schema-based classes. The proposed approach modifies the class declarations completely, making the language syntax follow XSD artifacts. Clearly, this is ill-suited not only for DTD and Relax NG, but also to the existing code base. Suppose you have back-end library that provides a lot of functions. All you need to do (in the approach proposed in this entry) to make this library work with XML - annotate the classes correspondingly. With Xen, you'll have to rewrite your library from scratch or create a data-mapping layer to map from new classes to old data classes. Looking into the future - the code as shown above, does not even know that it's working with XML. Tomorrow (a few years from now) this code can be converted to work with another data format (binary XML, OODBS) without touching the business logic, effectively separating the data layer from the business layer.

Porting small library from Java 5.0 to Java 1.4 - could it be any harder?

Posted by kirillcool on July 15, 2005 at 11:45 AM | Permalink | Comments (4)

And so, last evening I set out to provide 1.4-compatible version of my Substance look-and-feel library. What promised to be an easy task (a couple of changes per file, given not too excessive use of generics), turned out to be a frustrating and valuable experience not only with Swing classes, but with Java 5.0 features in general.

First off, I should say that at work we write for JDK 1.4.2. For me, last year that amounted to roughly 2600 hours spent on writing 1.4.2-compliant code. At home (for a few projects here on java.net) it's been 5.0 from the beginning. Last year that amounted to roughly 700 hours spent on using 5.0 features. Not claiming to know my way through all new features in 5.0 (there were way too many hands raised on Joshua Bloch's "Collections Connection" when he asked to see who knows everything in 5.0), I feel comfortable with all new language features. Contrary to what some claim (he looks suspiciously familiar), generics in the "light" version (no wildcards, no "super" or "extends" etc.) make the code look better and more robust. As a collection guru, you may think that nobody can forget that exact value type stored in some map, but what about your average programmer who left last week and whose bugs you need to fix? Your best hope would be that he documented the correct types of keys and values in javadoc, otherwise you would either hunt all the puts, or debug until your fingers are blue and ready to fall off.

And so, i thought. 77 files, 440KB of code. How hard can it be? It shouldn't even matter how many files or how many lines of code. Just take Retroweaver, add ant task and lean back. The "lean back" part ended abruptly, seeing that i have used quite a handful of methods that were new in 5.0 (which i used). Here lies the main deception of Retroweaver - its abilities are very limited. It can handle generics, enhanced for loops, enums, iterables, but that's about it. A class that's new in 5.0, or a new method on existing class - you're on your own. And that certainly discourages the use of Retroweaver - I moved to 5.0 not only because of the language features. I want to use new stuff like Formatter, Scanner, concurrency, ExecutorService and even String.replace that gets two CharSequences as parameters. And so, the first big disappointment was - if you want to port your library back to 1.4, you can't use any 5.0 new stuff. That sounds obvious, but not so if you start writing a new library straight in 5.0.

The next step was to see the functions that were not present in 1.4 and replace them with the corresponding counterparts from 1.4. That sounds easy, but when you extend functionality of the existing Java classes, it's not. For look-and-feel, you extend the existing UI delegates and plug-in your stuff. The stuff you plug should involve only drawing, so that couldn't change much between different JDK versions, right? Right and wrong. It doesn't change much, but a lot of stuff gets exposed in later JDK versions that was private in the previous ones. One of the best examples here would be createScrollButton in TabbedPaneUI. When you set your tabbed pane to work in SCROLL_TAB_LAYOUT mode, the tabs that overflow the available space are not visible, and can be "scrolled to" using two small arrow buttons. The default implementation draws the button on its own, ignoring the installed ButtonUI, so you would want to override this behaviour to provide consistent look and feel to all the buttons. In 5.0 it's very easy - you are override createScrollButton() function, it's called and then Swing plugs in all the listeners on those buttons. In 1.4 - big surprise. Pretty much everything in BaseTabbedPaneUI is private, the buttons, the listener, the layout. And everything is not only wired, but also hardcoded for the class names. And you can't make your button inherit from the inner button - its class is private. What you can do - the good old "copy paste". That's what is done in JGoodies - its TabbedPaneUI is a complete rip-off (with a few tweaks to plug its own buttons), spanning 3092 lines. My original 5.0 version of TabbedPaneUI is 235 lines. And that brought the second disappointment - there are very good reasons to use new functions. That sounds obvious too, but yet again if you start out with new functionality and then force yourself to go back, you'll have to implement it yourself.

At this point, the next step was obvious - the code must be rewritten. But should it still be in 5.0? That's a tough question. You can integrate Retroweaver in your ant script and run it everytime you change something. You can pay close attention to javadoc comments of every single function that you intend to use. You can set your compiler to 1.4 settings, but it will not catch use of new 5.0 functions. Or, you can just take your code and backport it to 1.4 yourself. After all, you are not using any new stuff (apart from generics and enums), and you are not burdened with all the bookkeeping.

A few clicks in Eclipse, and the project was configured to 1.4. Thirty seconds later, the build is over, and on 77 files I had 856 errors. About 30% were on generics (both parametrized collections and missing casts on retrieving objects), 30% were on @Override (you use that a lot writing look-and-feel under 5.0), and 35% were on enums. The most unexpected part was for enums - you just get used to having name(), values() and using them in switch statements. The harsh 1.4 reality sets in, and even when you use Bloch's enum pattern, you still need to provide name(), values() and rewrite your switch statements. Couple of hours later - all the errors were gone (except the functions that were not available in 1.4), and the code was uglier than before, especially when you need to store Longs in a Map (autoboxing is nice), or iterating over entries in Set or array. The third disappointment set in during the two hours frantic coding - removing 5.0 language features takes more that it seems. Less obvious (maybe because it's sold as "syntactic change" only), but time-consuming nonetheless.

When the last errors were fixed, I run the test application that tests all component UIs. Instantly i was greated with the long despised "use getRootPane().add()" message. Quick fix, the application runs, but:
  • background of menu and menu items is dark gray instead of nice light gray. The same for combobox.
  • default button behaviour is transferred to the clicked button.
  • the buttons are not "rollover-enabled"
  • dialog root pane is not opaque
  • selected items in list are not highlighted
  • sliders have background for ticks
  • progress bar is off by 1 pixel in both width and height
  • root pane header is off by 1 pixel in width
All these were either fixed or corrected for 5.0. Now i have to introduce "bug fixes" myself. That's when the last disappointment sets in - each new JDK version fixes existing bugs in older versions. Obvious as well, but quite unexpected when all you want is to run your library in older JDK and just want it to show the same behaviour.

With all of the above, twenty four hours later, the library has not been ported back, but a few lessons have been learned.

POJO - Complex New Perl Classes

Posted by kirillcool on April 02, 2005 at 08:29 AM | Permalink | Comments (5)

Over the last year the term POJO has enjoyed an enormous success in the Java community. One of the major reasons for this being, of course, introduction of annotations in JDK 5.0. The new version of EJB shows us the promised land where no home or remote interface step, JAXB 2.0 works exclusively with annotations, and there is even a whole JSR that aims to standardize common annotations in Java.

But i wonder how exactly a Java class seasoned with assorted annotations qualifies as POJO. Let's stop for a minute and analyze the abbreviation itself (well, i know that it sounds very sexy, and has a lot of connotations, no pun intended).
  • The P stands for plain. A java class with annotations is quite far from being plain. The annotations can be quite complex (take a look at the JAXB 2.0 generated classes).
  • The O stands for old. Apart from the obvious (that the annotations are only part of JDK 5.0), i don't see many examples of new JSRs that propose to work with classes that aren't annotated at all.
  • The J stands for java. More on this later in the entry, but annotations make a class to be non-standard Java (in my view).
  • The O stands for object. The annotations are on classes, and not on a single instance, but this may be more suitable for BileBlog incoherent blabbering.
And now to the main problem of annotations. As advocated by many (including the vehement supporters of EJB 3.0), the annotations were devised to make our lives easier. No more need for complex XML configuration files, no more need for getter and setters that conform to bean standard, no more need for marker interfaces and so on. Just mark your field / method / class with an annotation, and you get all that you could before at half a price. But what exactly have we achieved?
  • The configuration XML file is now spread all over our class model. Furthermore, once you compiled your classes, you can't configure annotations (correct me if i'm wrong).
  • The bean getters and setters are gone, but instead the corresponding fields are marked with a predefined annotation. You say potato, and i say @potato.
  • The marker interfaces are gone, but instead the corresponding classes are marked with a predefined annotation. You say tomato, and i say @tomato.
  • Everybody is welcome to create his own annotations. Everybody can finally feel like a king and create his own kingdom. Everybody can finally create his own flavour of Java. Everybody does. Sun realizes that and tries to come up with common annotations. The community happily ignores the JSR. It becomes impossible to read the code because each field and class has more than 10 annotations attached to them. Java becomes Perl.




Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds