Tuesday, May 17, 2016

JAVA and XML - The Ultimate Magic

I've a lot of issues but I've decided to play with XML file i.e. create XML, parse XML etc.

JAVA provides several options for handling XML structures and files. One of the most common and used ones is JAXB.

JAXB = JAVA Architecture for XML Binding.

It offers the possibility to convert JAVA objects into XML and the vice versa i.e. convert XML into JAVA objects!!!!

It provides annotations to map java fields to a XML structure i.e. node, attribute, node order, root, hierarchy, nesting etc.

Few of mostly used annotations:

  • @XmlRootElement as root element.
  • @XmlElement in combination with setter methods i.e. the node name.
  • @XmlAttribute to pass attributes to the XML nodes. These attributes can have properties like to be required or not.
  • @XmlType to indicate special options like to order of appearance in the XML.
Now I'm going to technical hands on.

1. Covert a Java Object into a XML file called Marshaling.

2. Convert / Load XML file into a JAVA object Un-Marshal.

3. Handling complex data type which is not directly available in JAXB. 



1. Marshaling


At 1st i'll create a class Country having a JAXB annotation indicating the root,  few variables/fields and getter setter methods. No any complex things just a normal class which you can create in your 1st day of java learning.


import javax.xml.bind.annotation.XmlRootElement;


@XmlRootElement(name="country")
public class Country {
    
    private String name,capital;
    
    private int population;

   
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCapital() {
        return capital;
    }

    public void setCapital(String capital) {
        this.capital = capital;
    }

    public int getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }
     
}



Now  i'll create a sample XML file for our Country class. For this purpose, i'll create a test main program using JAXBContext and its marshaling functionalities which will use Country class..

The most important part is JAXBContext class. This provides framework for validate, marshal, un-marshal XML file into java object and vice versa.



public class XMLMagic {

   
    public static void main(String[] args)  {

        // intialize country class

        Country bd = new Country();
        
       bd.setName("Bangladesh");

        bd.setCapital("Dhaka");

        bd.setPopulation(160000000);

        try {
            /* init jaxb marshaler */
            JAXBContext context =    JAXBContext.newInstance(Country.class);

        Marshaller marshaler = context.createMarshaller();
            
            /* set this flag to true to format the output */
            marshaler.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            
            /* marshaling of java objects in xml (output to file and standard output) */
            marshaler.marshal(bd, new File("country.xml"));       
     
             // For JAVA Version 6, include following instead of previous line

            /*marshal.marshal(bd, new FileWriter("country.xml")); */           



            /*The following is for console output. You can remove following line*/
            marshaler.marshal(bd, System.out); 

        } catch (JAXBException ex) {
            ex.printStackTrace();
        }
        
    }
}

The output is country.xml.



If any change, required in XML I only change in country class. No change in main class.

Now i want to change the node ordering in XML file. i.e. population, name, capital.


So i just add the following line (XML) in top of the Country class.

@XmlType(propOrder={"population", "name", "capital"})


You've to specify all property in @XmlType.propOrder otherwise IllegalAnnotationsException will be raised.



Run the main program declared in previous section. The output is as follows:



Now i'll change Node name using XmlElement annotation.

@XmlElement (name="####")




After Run the main program, country.xml should be as follows:




Now I'll add an Attribute. So I do following:

- Add a class variable: importance

- getter/setter methods for that variable

- Use annotation: XmlAttribute

- No propOrder is required for Attribute.


The code is as follows:


private int population, importance;


public int getImportance() {
        return importance;
    }

@XmlAttribute(name="imp", required=true)
public void setImportance(int importance) {
        this.importance = importance;
    }


@XmlAttribute annotation specifies the attribute of parent node. required=true means  the

 attribute must be present in parent node even if the value is not set.


Run main program. The country.xml:



Now i want to iterate a node containing String. For example, I'll iterate cities in country.

Add highlighted code in Country.java



And modified main class is as follows:



The country.xml is as follows:


Now I'll use wrapper node around the city collections. So I use @XmlElementWrapper annotation to wrap city nodes. Just add the highlighted line.


After run main program, country.xml is as follows.


Now i'll show how to nesting nodes from collection of nested classes.


- Create 2 more classes : Place.java & Location.java

- In Country class there is List of Place class

- In Place class, there is a List of Location class

- I use @XmlSeeAlso annotation when we want another class included in the XML output

The Country.java is as follows:


package com.xml.node;

import java.util.ArrayList;

import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlType;

/**

 *
 * @author Dell
 */

@XmlType(propOrder={"population", "name", "capital", "cities", "places"})

@XmlRootElement(name="country")
@XmlSeeAlso({Place.class})
public class Country {
    
    private String name,capital;
    
    private int population, importance;
    
    private List<String> cities;
    
    private List<Place> places;

    public Country() {

        cities = new ArrayList<String>();
        places = new ArrayList<Place>();
    }

    public List<Place> getPlaces() {

        return places;
    }

    @XmlElement(name="place")

    public void setPlaces(List<Place> places) {
        this.places = places;
    }

    

    public List<String> getCities() {
        return cities;
    }

    @XmlElementWrapper(name="cities")

    @XmlElement(name="city")
    public void setCities(List<String> cities) {
        this.cities = cities;
    }

    public String getName() {

        return name;
    }

    @XmlElement(name="countryName")

    public void setName(String name) {
        this.name = name;
    }

    public String getCapital() {

        return capital;
    }

    @XmlElement(name="countryCapital")

    public void setCapital(String capital) {
        this.capital = capital;
    }

    public int getPopulation() {

        return population;
    }

    @XmlElement(name="countryPopulation")

    public void setPopulation(int population) {
        this.population = population;
    }

    public int getImportance() {

        return importance;
    }

    @XmlAttribute(name="imp", required=true)

    public void setImportance(int importance) {
        this.importance = importance;
    }
    
}


The Place.java is as follows:


package com.xml.node;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;



/**
 *
 * @author Dell
 */

@XmlType(propOrder={"name", "address", "lattitude", "locations"})
public class Place {
    private String name;
    private String address;
    private double lattitude;
    
    private List <Location> locations;

    

    public Place() {
        locations = new ArrayList<Location>();
    }

    public List<Location> getLocations() {
        return locations;
    }

    public void setLocations(List<Location> locations) {
        this.locations = locations;
    }

    
    
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public double getLattitude() {
        return lattitude;
    }

    @XmlElement(name="lat")
    public void setLattitude(double lattitude) {
        this.lattitude = lattitude;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

The Location.java is as follows:


package com.xml.node;

/**
 *
 * @author Dell
 */
public class Location {
    private String name, address;

    public Location() {
        name="LOCATION";
        address = "ADDRESS";
    }

    
    
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }        
}


The Main Program is as follows:

public static void main(String[] args) {

        
        Country bd = new Country();
        
        bd.setCapital("Dhaka");
        bd.setName("Bangladesh");
        bd.setPopulation(160000000);
        bd.setImportance(2);
                
        bd.getCities().add("Dhaka");
        bd.getCities().add("Rajshahi");
        bd.getCities().add("Sylhet");
        bd.getCities().add("Chittagong");
        
        Place place1 = new Place();
        
        place1.setAddress("DHAKA"); place1.setLattitude(10.5); place1.setName("ZOO");
        
        place1.getLocations().add(new Location());
        place1.getLocations().add(new Location());
        place1.getLocations().add(new Location());
        
        bd.getPlaces().add(place1);
        
        Place place2 = new Place();
        place2.setAddress("RAJSHAHI"); place2.setLattitude(20.99); place2.setName("ZOO");
        
        place2.getLocations().add(new Location());
        place2.getLocations().add(new Location());
        place2.getLocations().add(new Location());
        
        bd.getPlaces().add(place2);
        
        try {
            // TODO code application logic here
            
            JAXBContext context = JAXBContext.newInstance(Country.class);
            
            Marshaller marshal = context.createMarshaller();
            
            marshal.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            
            marshal.marshal(bd, new FileWriter("country.xml"));

            marshal.marshal(bd, System.out);

        } catch (IOException ex) {
            Logger.getLogger(XMLMagic.class.getName()).log(Level.SEVERE, null, ex);
        } catch (JAXBException ex) {
            Logger.getLogger(XMLMagic.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        

    }

Output Country.xml is as follows:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<country imp="2">
    <countryPopulation>160000000</countryPopulation>
    <countryName>Bangladesh</countryName>
    <countryCapital>Dhaka</countryCapital>
    <cities>
        <city>Dhaka</city>
        <city>Rajshahi</city>
        <city>Sylhet</city>
        <city>Chittagong</city>
    </cities>
    <place>
        <name>ZOO</name>
        <address>DHAKA</address>
        <lat>10.5</lat>
        <locations>
            <address>ADDRESS</address>
            <name>LOCATION</name>
        </locations>
        <locations>
            <address>ADDRESS</address>
            <name>LOCATION</name>
        </locations>
        <locations>
            <address>ADDRESS</address>
            <name>LOCATION</name>
        </locations>
    </place>
    <place>
        <name>ZOO</name>
        <address>RAJSHAHI</address>
        <lat>20.99</lat>
        <locations>
            <address>ADDRESS</address>
            <name>LOCATION</name>
        </locations>
        <locations>
            <address>ADDRESS</address>
            <name>LOCATION</name>
        </locations>
        <locations>
            <address>ADDRESS</address>
            <name>LOCATION</name>
        </locations>
    </place>
</country>



2. Un-Marshaling

You can un marshal (load XML file into object) country.xml by using following lines:

try {
            JAXBContext context = JAXBContext.newInstance(Country.class);
            Unmarshaller unmarshal = context.createUnmarshaller();
            
            File file = new File("country.xml");
            
            Country country = (Country) unmarshal.unmarshal(file);
            
            System.out.println(country);
            
        } catch (JAXBException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }





3. Handling Complex Data Type Using Adapter

If we want to marshaling complex data type such as java.util.Date into a specific format, then we've to write an adapter class to indicate JAXB how to manage.

The class must extends from XmlAdapter class and implements following abstract methods of this class:

- marshal &
- unmarshal

I've write the following Adapter class:


import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends  XmlAdapter<String, Date> {

    @Override
    public String marshal(Date v) throws Exception {
        
        SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
        return sdf.format(v);
    }

    @Override
    public Date unmarshal(String v) throws Exception {
        Date dt = new SimpleDateFormat("dd-MM-yyyy").parse(v);
        return dt;
    }
   
}

And in Country.class I've add following:




Add the following 2 lines in Main program to see the value of foundationDate node in XML:

Date dt = new Date("26-MAR-1971");
        
 bd.setFoundation(dt); 




Run the Main program to check the output:




And that's all about this topic.



2 comments: