2004-10-17 The Java Specialists' Newsletter [Issue 097] - Mapping Objects To XML Files Using Java 5 Annotations
Author: Amotz Anner
JDK VERSION: Sun JDK 1.5.0-B64
Category: Language
You Can Subscribe from Our Home Page:
Http://www.javaspecialists.co.za (Which Also Hosts All Previous Issues, Available Free of Charge :-)
Welcome to the 97th edition of The Java (tm) Specialists' Newsletter. We had a beautiful day down here in Cape Town, South Africa. This morning I was concerned because it was cloudy, but fortunately it cleared up for our braai. In South africa, a popular pass-time is to make a fire with wood, then once this has burnt down, to grill some lamb chops, sausage or chicken on the coals. We only braai once the coals are completely burnt, to avoid having flames leap up and burn the meat. The coals have to be so hot that you can not hold your hand at the level of the meat for more than eight seconds. Some wood holds its heat longer, and you must make sure that the flavour of the meat is not adversely affected by the wood you choose. The braai is more for the social interaction than for the food and so it is common to start the fire only after the guests have arrived. Another interesting tradition is to invite guests for a braai, but ask Them to bring their own meat and lots, and maybe a salad. We call That A Bring-and-braai. The Company is Very Important, And Althought WE Sometimes Braai on Our OWN, We Usually Prefer to have lots of happy friends
So, if you visit South Africa, and tell me when you will be in Cape Town, you will probably be invited to a "braai". We will probably ask you to bring some of the food, and when you arrive, I will still be chopping the wood, but besides that, it is a lot of fun, and you should accept the invitation:) I find it amazing how quickly some people pick up a new technology Amotz Anner sent me an example of how you can use annotations. TO WRITE SOME VERY INTERESTING CODE. THAN AMOTZ DID ME A HUGE FAVOUR AND WROTIN NEWSLETTER. THANK You VERY VERY MUCH.
Amotz is the founder of x.m.l. Systems Ltd Located on: 46, Jerusalem St. Flat 9BKFAR-SABA 44369ISRAELCELL: 972 (54) 686-0707
That was not the only interesting communication with Amotz. He discovered that in the JDK 1.5.0, if you compile code that uses the ternary if-else operator, with one side of the results null then it can deliver wrong results if you immediately assign THIS COMPILER BUG HAS BEEN FIXED AND WILL BUG HAS BEEN FIXED AND WILL BE AVAILALALALALS, PLEASE LOOK ATUR POSTS ON JAVALOBBY AND THESERVERSIDE.
This bug does concern me. With most Java bugs that I have seen, upgrading to a new JRE solves the problem. With this bug, you need to upgrade AND recompile all your sources. It is easy for this bug to make it into your production Code Library, And Unless!, You Might Never Pick IT Up. So, Be Warned!
ENOUGH from Me, Over to Amotz ...
Mapping Objects to XML Files Using Java 5 Annotations
............ ..
My code relies on XML to declare all sort of components, and then have Java classes construct themselves from those declarations It is NOT a persistence framework, for the following reason:. I consider the XML declaration to be primary, and the Java class to be secondary, a mere tool to realize the declaratory intent of the XML document. in contrast, in a persistence framework, the Java class is primary and the XML document is just a vehicle used to contain persistence data, and has no standing in and of itself .
.
Prior to annotations, I was severely limited in my choices. What I could, and did do, was to use reflection to look for all public fields of a class whose names match those of an XML attribute in the appropriate declaration, and initialize those fields from the attribute value. This was a fragile solution, since there was no clear indication as to the special status of the names of those fields, and all to often I broke my code by changing field names, thus breaking the connection to the XML declaration .
THEN CAME ANNOTATIONS AND SOLVED ALL MY Problems in One Fell Swoop. With Them i CAN:
Clearly indicate which fields are initialized from an XML declaration Dissolve the field name -.. Attribute name bond Extend usage from just XML attributes to XML elements as well Supply centrally / locally defined default values..
SO HOW Is That Magic Achieved? In Four Easy Steps, Of Course.
Step 1: annotation is defined
First, An Annotation IS Defined, In The Same Way As an Interface Would Be, As Follows:
Import java.lang.annotation. *; / **
* Make Annotation Available At Run Time and ONLY Allow Fields To
* be modified by this annotation.
*
* WE Have 3 Properties, Defaults for All
* /
@ReTENTION (RETENTIONPOLICY.RUNTIME)
@Target (ElementType.field)
Public @Interface fromxml {
/ **
* NORMALLY, THE FIELD'S VALUE I Taken from an attribute with
* an identical name. xpath can be used to specify a differentent
* Source, Breaking The name Linkage
* /
String xpath () default ""
/ **
* A Default value to be used if field source is not found in
* The XML (We call it "DFLT" Since "default" is reserved)
* /
String dflt () default "";
/ **
* Flag to Trim Fetched Data.
* /
Boolean Trim () Default True;
}
Step 2: FIELDS Are Annotated
SECOND, FIELDS Have to Be Annotated. Syntactical, Annotations Are Modifiers, So The Result Looks Like:
// this is equivalent to old usage.
@FromXML public int A;
// this is the new usage.
@Fromxml (xpath = "a / b / c", dflt = "blue") public string color;
Step 3: Call Initializer
THIRD EASY Step is to call the initializer with a class and element references:
Xmlconstructor.constructFromxml (this, elem, false);
A More Complete Example Is Our Componenslider Class That Manages A Slider That Can Be Configured Used The Dom4j Jar File to Get THIS.
Import org.dom4j.element;
Import javax.swing. *;
Public class componentslider {
@Fromxml private boolean inverted = false;
@Fromxml private INT min = integer.min_value;
@Fromxml private int max = integer.min_value;
@Fromxml private INT MinortickInterval = Integer.min_Value;
@Fromxml private = integer.min_value; @Fromxml private boolean snaptotick = false;
Public Componentslider (Jslider Slider, Element DEF) {
Xmlconstructor.constructFromXML (this, def);
Slider.seTminimum (min);
Slider.setmaximum (max);
Slider.setinVerted (Inveted);
IF (MinortickInterval! = Integer.min_Value) {
Slider.setminortickspacing (minorticinterval);
Slider.SetPainTicks (TRUE);
Slider.setSnapticks (Snaptotick);
}
IF (MajortickInterVal! = Integer.min_Value) {
Slider.setmajortickspacing (MajortickInterval);
Slider.SetPainTicks (TRUE);
Slider.SetPaintLabels (TRUE);
Slider.setSnapticks (Snaptotick);
}
}
}
Step 4: Supply ConstructFromxml Method (ONCE)
And Finally, But Just Once, The Above Method Has To Besu Supplied. It looks like:
Import org.dom4j. *;
Import java.lang.reflect.field;
Public class xmlconstructor {
Public Static Void ConstructFromxml (Object Obj, Element ELEM) {
ConstructFromxml (Obj, elem, false);
}
/ **
* Set Object's Fields from Values of XML Declarations, Using
* "@Fromxml" Annotation
*
* Scans All Fields in An Object for a Annotated Elements, And
* Initialize Them, According to the Fields Type.
*
* @Param Usesuper if super class is to be processed
* @Param o the Object to Scan for Fields
* @Param Element The Element Whose Attributes Are The Data
* Sources.
* /
Public Static Void ConstructFromx (Object O, Element Element,
Boolean buyuper) {
IF (Element == Null) {
Return;
}
Class Aclass = GetAppropriateClass (O, Usesuper);
FOR (Field Field: aclass.getdeclaredfields ()) {
Fromml fromxml = field.getannotation (fromxml.class);
IF (fromxml! = null) {
Field.setAccessible (TRUE); HandleanNotatedField (FromML, Element, Field, O);
}
}
}
Private static void handleannotatedfield (fromxml fromxml,
Element Element,
Field Field, Object O) {
String value = getValue (fromxml.xpath (), element, field, proxml;
IF (! iSempty (value)) {
IF (fromxml.trim ()) {
Value = value.trim ();
}
SetField (Field, O, Value);
}
}
Private Static String getValue (String XPath, Element Element,
Field Field, fromxml fromxml) {
String value = NULL;
IF (! iSempty (xpath)) {
Node node = element.selectsinglenode (xpath);
IF (node! = null) {
Value = node.getText ();
}
} else {
// if no xpath, us name
// Get Value of Matching Attribute
Value = element.attributevalue (field.getname ());
}
IF (value == null) {
// use default
Value = fromxml.dflt ();
}
Return Value;
}
Private static void setfield (Field Field, Object O, String Value) {
Class Type = Field.gettype ();
Try {
// Now Initialize Field According to Type
IF (type.equals (int.class)) {
Field.setint (O, Ashexint (Value));
} else if (Type.Equals (string.class)) {
Field.set (o, value.intern ());
} else if (Type.Equals (double.class) {
Field.SetDouble (O, Double.Parsedouble (Value));
} else if (type.equals (boolean.class) {
Field.setBoolean (O, Asboolean (Value));
} else if (Type.Equals (char.class)) {
Field.SetChar (o, value.charat (0));
}
} catch (IllegaCcessException ex) {
// this stay not happen, Since We are setting the access
// TO TRUE
Throw new runtimeException (ex);
}
}
Private Static Class GetAppropriateClass (Object O, Boolean Usesuper) {
Class ACLASS = O.GetClass ();
IF (usesuper) {
Aclass = aclass.getsuperclass ();
}
Return Aclass;
}
Private static boolean iSempty (string test) {return test == null || test.length () == 0;
}
/ **
* Use Hex Conversion if string starts with "0x", else decimal
* Conversion.
* /
Private static int ashexint (string value) {
IF (Value.tolowerCase (). StartSwith ("0x")) {
Return INTEGER.PARSEINT (Value.substring (2), 16);
}
Return Integer.Parseint (Value);
}
Private static boolean asboolean (String Option) {
IF (! iSempty (option)) {
String Opt = option.tolowercase ();
Return "YES". Equals (OPT) || "on" .equals (OPT)
|| "True" .equals (OPT) || "1" .Equals (OPT);
}
Return False;
}
}
Very Simple, Really.
Next, We create an XML File That Contains The Attributes for the Slider Class:
XML Version = "1.0" encoding = "UTF-8"?>
XSI: NonamespacesChemAlocation = 'file: / c: /Xml sys/xml sys/xsd/components.xsd' Min = "20" max = "180" minorticinterval = "2" MajortickInterval = "10"> Slider> And An Example Class That Uses The ComponentsLider: Import org.dom4j. *; Import org.dom4j.io.saxreader; Import javax.swing. *; Import java.io. *; Public class annotationDemo extends jframe { Private Jslider Slider = new jslider (); Public AnnotationDemo (Element Sliderdef) { New Componentslider (Slider, SliderDef); // HK: DID you notice That You don't have to say: // getContentPane (). Add (...) in jdk 1.5 Anymore? Add (Slider); } Private static element loadsliderxmlfile (String filename) Throws filenotfoundexception, DocumentException { // the slider definition as xmlelement sliderdef = NULL; // a Reusable SAX Parser SAXReader Xmlreader = New SaxReader (); XmlReader.setignoreComments (TRUE); XmlReader.SetMergeAdjacentText (TRUE); XmlReader.setStripWhitespaceText (TRUE); File File = New File (filename); IF (file.exiss () && file.canread ()) { Document Doc = XmlReader.read (New FileInputStream (file)); Sliderdef = doc.getrootelEment (); } IF (sliderdef == NULL) { Throw new IllegalargumentException "Could Not Find XML Declaration"); } Return Sliderdef; } Public static void main (string args []) throws exception { Element Sliderdef = LoadSliderXMLFile (Args [0]); AnnotationDemo frame = new annotationDemo (SliderDef); Frame.setsize (500, 100); frame.setDefaultCloseOperation (exit_on_close); Frame.setLocationRereto (NULL); // Center the frame Frame.setVisible (TRUE); } } I have not done it yet, but I can now initialize multi-dimensional arrays, lists, collections or what-not by straightforward extensions of the above code. Obviously, it made no sense before annotations, as XML attributes appear at most once. But With elements, there is no claim limited data queries and / or iv. LEFT AS Another Annotation Which Would Be Used To Associate "Slider" Directly with "WeightsLider.xml", Making Annotation Usage Consistent, Well Document and Safe. Have fun. Amotz P.S. I (Heinz) Refactored Amotz's Code A Bit, SO if you find bus or do not like the style of the code, blame me :) Copyright 2000-2004 Maximum Solutions, South Africa