HierarchicalMap Tutorial - Intermediate Level
HierarchicalMap is an interface. So anyone can implement it. This tutorial is based on BasicHierarchicalmap, a reference implementation of HierarchicalMap.
Following subjects are covered in this tutorial:
(Javascript must be enabled to allow syntax highlighting for source code snippets in this tutorial)
Intermediate Level
6. Working with StreamHierarchicalMap can be streamed in two ways: Binary format and XML format. The package org.dhmp.io contains classes that help stream handling:
Writing and reading a HierarchicalMap is straight forward:
HierarchicalMap map = new BasicHierarchicalMap(); map.put("Greeting", "Hello World!"); try { MapOutputStream mout= new MapOutputStream( new FileOutputStream("/temp/map.dat")); mout.writeMap(map); mout.close(); MapInputStream min = new MapInputStream( new FileInputStream("/temp/map.dat")); HierarchicalMap newMap = min.readMap(); } catch (Exception e) { e.printStacktrace(); } System.out.println(newMap.get("Greeting"));
For serialization, it is preferable to use Java's native mechanism instead of using above class. Also note that this class uses internal cache to avoid infinite loop when the map contains recursive reference. Therefore, try to renew the class from time to time and avoid keeping it opened for long period of time such in server usage.
There are also three other method to read/write a HierarchicalMap: readMap(start), readMap(start, stop) and readMapUntil(stop).
The MapInputStream traverses the structure during read operation. There is an imaginary cursor that lies between nodes. When "start" key is specified, actual read operation begins only when this imaginary cursor position matches the "start" key. In the similar way, the "stop" key tells to the MapInputStream to finish reading just before the matching "stop" key.
The next figure shows what happens when the operation readMap("A","B/F") is issued on the following structure:
Notice that reading starts right after imaginary cursor position (node "A"). And finishes just before the imaginary cursor position (node "F"). Be aware that "stop" key is relative from "start" key and only nodes are considered for matching these keys.
//populating map HierarchicalMap map = new BasicHierarchicalMap(); HierarchicalMap map2 = map.add("A"); HierarchicalMap map3 = map2.add("B"); HierarchicalMap map4 = map3.add("C"); map4.add("D", "value D"); map4.add("E", "value E"); map4 = map3.add("F"); map4.add("G", "value G"); map4.add("H", "value H"); map3 = map2.add("I"); map4 = map3.add("J"); map4.add("K", "value K"); //readMap(start, stop) behavior try { //streaming above map into byte array ByteArrayOutputStream buffer = new ByteArrayOutputStream(); MapOutputStream mout = new MapOutputStream(buffer); mout.writeMap(map); mout.close(); //reading readMap("A","B/F"); MapInputStream min = new MapInputStream( new ByteArrayInputStream(buffer.toByteArray())); HierarchicalMap ret = min.readMap("A","B/F"); System.out.println("readMap(\"A\",\"B/F\"): " + ret); //reading remainder maps System.out.println("readMap(): " + min.readMap()); System.out.println("readMap(): " + min.readMap()); min.close(); } catch (IOException ex) { ex.printStackTrace(); }
The lines above will produce the following output:
readMap("A","B/F"): <B><C><D>value D</D><E>value E</E></C></B> readMap(): <F><G>value G</G><H>value H</H></F> readMap(): <I><J><K>value K</K></J></I>
As mentioned earlier, only nodes are considered. Therefore calling readMap("A","B/C/E") instead of readMap("A","B/F"), will result in reading entire map, because there is no "stop" key node ("B/C/E" is a leaf) in the structure.
7. Working with XMLWorking with XML can be done in very similar way. This implementation of HierarchicalMap uses Apache's Xerces project for XML handling.
XML is a powerful data structure with many concepts like documents, elements, attributes, namespaces and so on. The HierarchicalMap has only the nodes (another HierarchicalMap) and leaves (any other Objects). Therefore, some simplification must be done to be able to map XML into HierarchicalMap.
XML file below will be used for following examples.
<Root_Element Root_attribute="roots attribute"> roots text <Element/> <ElementWithValue>elements value</ElementWithValue> <ElementWithChild> <Element Attrib1="a1" Attrib2="a2"/> <Element Attrib3="a3" Attrib4="a4">e0</Element> <Element Attrib5="a5" Attrib6="a6"> <Element1>e1</Element1> </Element> <Element Attrib7="a7" Attrib8="a8"> <Element2>e2</Element2> asd hjk <Element3 Attrib9="a9" Attrib10="a10">e3</Element3> def ghi jkl mno </Element> <Element Attrib11="a11" Attrib12="a12">e1</Element> </ElementWithChild> </Root_Element>
Create a text file containing above XML. Name it "sample.xml" and put it in "/temp" directory for further examples.
Reading XML file into HierarchicalMap is similar to reading a binary stream.
try { FileInputStream fin = new FileInputStream("/temp/sample.xml"); XMLMapInputStream xin = new XMLMapInputStream(fin); HierarchicalMap map = xin.readMap(); System.out.println(map); } catch(Exception e) { e.printStackTrace(); }
The result of above code is a long line of string like following:
<Root_Element><Root_attribute>roots attribute ...
Copy the result into text file and save it as "result.xml". Then open it with an browser or any other XML aware editor. The first portion will be like the following:
- <Root_Element> <Root_attribute>roots attribute</Root_attribute> <_Element_Value>roots text</_Element_Value> <Element /> <ElementWithValue>elements value</ElementWithValue> - <ElementWithChild> - <Element> <Attrib1>a1</Attrib1> <Attrib2>a2</Attrib2> </Element> - <Element> <Attrib3>a3</Attrib3> <Attrib4>a4</Attrib4> <_Element_Value>e0</_Element_Value> </Element> ...
The result is slightly different from original XML file:
Usually this simplification does not affect the interpretation of the data contained in the XML. But sometimes it is necessary to preserve the XML structure. The only way to do that using HierarchicalMap is by wrapping each type of leaf with distinct classes. There are some wrapper classes for this purpose: org.dhmp.util.xml.Attribute for attributes and org.dhmp.util.xml.ElementValue for element's values.
XMLMapInputStream has an option to automatically wraps nodes using these classes.
try { FileInputStream fin = new FileInputStream("/temp/sample.xml"); XMLMapInputStream xin = new XMLMapInputStream(fin); //option for preserving XML structure xin.init("<Param pure='true'/>"); HierarchicalMap map = xin.readMap(); FileOutputStream fout = new FileOutputStream("/temp/result2.xml"); XMLMapOutputStream xout = new XMLMapOutputStream(fout); xout.writeMap(map); xout.close(); } catch(Exception e) { e.printStackTrace(); }
Note the line "xin.init("<Param pure='true'/>")". This instructs the XMLMapInputStream to preserve the XML structure wrapping the leaf using wrapper classes when required. The XMLMapOutputStream recognizes the wrapper classes and converts to corresponding XML syntax. Open the "sample.xml" and "result2.xml" using browser and verify that there is no difference between them.
Another usage of XMLMapInputStream can be like following:
public void init(String config) { XMLMapInputStream in = new XMLMapInputStream( new ByteArrayInputStream(config.getBytes())); HierarchicalMap paramMap = (HierarchicalMap)in.readMap().get("Param"); init(paramMap); } public void init(HierarchicalMap paramMap) { String param; param = (String)paramMap.get("pure"); if(param != null) { handlePureXML = (param.toLowerCase() .equals("true"))?true:false; }
The above snippet of code is used in XMLMapInputStream initialization to change its behavior.
Continue to Intermediate Level - Handling Large XML