Dynamic Class loading from XML

Aviras 1 Tallied Votes 389 Views Share

Hello everyone, this is something I wrote a couple months back. It is used as a universal class/object loader from an XML file. For the handling of XML I use the JDOM library. The story basically goes like this: I have a load of classes in a game I'm working on, going from enemies to items to zones, you name it. That data is written in xml files.

Instead of writing a different method for every class and every constructor, what these methods do is: you start with for instance loadItems(File aFile), which takes the xml file for Items as an argument. Using JDOM, the root of the document is found, and the method iterates over all the children, which would be all my seperate objects. The jdom.Element is given together with the class the returned object has to have as an argument to loadData. Using the parseData function I parse the strings of every child of the given Element, to identify it as an int, a double, int array, whatever. The types of all these variables get identified, and according to the class you have to the method, it looks up the right constructor, and returns you the desired object.

I now just have loadItems, loadEnemies, loadTowns, etc, methods of less than 10 lines, that all use the loadData method to store my objects in the database.

public void loadItems(File aFile){
		Equipment testItem;
		try {
			Document doc = parser.build(aFile);
			Element root = doc.getRootElement();
			List<?> objects = root.getChildren();
			Iterator<?> i = objects.iterator();
			while(i.hasNext()){
				testItem = (Equipment)Global.rwtext.loadData(Equipment.class, (Element)i.next());
				//equipment is a HashMap
				equipment.put(testItem.getID(),testItem);
			}
		} catch (JDOMException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public Object parseData(String s){
		// try to split strings into multiple strings in case of multiple IDs
		// in case of a single int, 'ints' will contain the one int
		String[] ints = s.split(";");
		try{
			ArrayList<Integer> IDs = new ArrayList<Integer>();
			for(String str: ints){
				IDs.add(Integer.parseInt(str));
			}
			//so it later doesn't get recognised as an int array, but just an int
			if(IDs.size() == 1){
				return (int)IDs.get(0);
			}
			return convertIntegers(IDs);
		} catch(NumberFormatException e){// thrown when s is a string or double, float..
			ints = null;
			try{
				return Double.parseDouble(s);
			} catch(NumberFormatException nfe){
				if(s.equalsIgnoreCase("true")){
					return true;
				}
				else if(s.equalsIgnoreCase("false")){
					return false;
				}
				return s;
			}
		}
	}

	public Object loadData(Class<?> cl, Element el){
		// creates a class of given type, coming from the XML element el
		
		ArrayList<Object> constrParam = new ArrayList<Object>();
		@SuppressWarnings("unchecked")
		List<Element> properties = el.getChildren();
		// id is given as attribute for xquery for easy lookup, so add id to constrparam first
		constrParam.add(Integer.parseInt(el.getAttributeValue("id")));
		Iterator<Element> i = properties.iterator();
		while(i.hasNext()){
			Element child = i.next();
			// if child has more children, those are dealt with in the constructor, child is passed as a parameter
			if(!child.getChildren().isEmpty()){
				constrParam.add(child);
			}
			else{
				constrParam.add(parseData(child.getText()));
			}
		}
		Class<?>[] paramClass = new Class[constrParam.size()];//soorten parameters
		for(int j = 0;j<paramClass.length;j++){
			paramClass[j] = constrParam.get(j).getClass();
		}
		try {
			Constructor<?> objCtor = cl.getConstructor(paramClass);
			return objCtor.newInstance(constrParam.toArray());
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static int[] convertIntegers(ArrayList<Integer> integers)//ArrayList -> int[]
	{
		int[] ret = new int[integers.size()];
		for (int i=0; i < ret.length; i++)
		{
			ret[i] = integers.get(i).intValue();
		}
		return ret;
	}
JamesCherrill 4,733 Most Valuable Poster Team Colleague Featured Poster

That's really interesting, and thanks for sharing it with us all.
How would you compare it (pros, cons) with java.beans.XMLDecoder?

Aviras 5 Junior Poster in Training

I don't have a lot of experience with the XMLDecoder, but what I can tell is JDOM gives a lot of freedom. Besides doing that, it works with a mindset I like. A wise man once said "Make things as simple as possible, but not simpler". JDOM gives you a lot of options, without causing too much overhead or giant amount of function calls.

It also allows for more freedom in writing the xml file itself. XMLDecoder is often used after creating an xml file using XMLEncoder as I understand it, and is used rather with an inputstream. All my objects aren't hardcoded ofcourse, and I write the xml from scratch. It allows me to write variable names the way I want it to, which is handy when dealing with loads of data.

JDOM actually lets you build the tree and traverse it, and the Element class is one I couldn't do without. For instance, since Java requires you to make a different constructor for a different amount of variables, instead of using one and fill in the blanks in the constructor itself, like Python f.e., the Element class allows me to make only one constructor, taking the Element as an argument, and treat it in the constructor dynamically. I don't immediatly see how I would do that with XMLDecoder, but that might be me. Again, I have pretty much no experience with XMLDecoder, I did some research for what xml parser to use, I used a stax-like for like 2 hours before I got rid of it, and jdom came out as the big winner, and haven't looked back. XMLDecoder seems like an efficient tool to use with storage and reading of automatically generated data though.

Either way, thanks for the constructive comment. :)

ejosiah 4 Junior Poster

@Aviras you didn't have to write all this code to load objects via xml; Apache commons digester is a library for OXM (object xml mapping). we don't want to reinvent the wheel now do we.

Aviras 5 Junior Poster in Training

That's an interesting find you have there. It seems to give quite some freedom as well, but I only looked at it briefly. Either way, this is still a quite simple approach (in terms of LOC, the actual procedure is 25 lines), and full control of what it does without having to go through the Core API.

No need to be condescending though.

Since I not only use xml to load my objects, using jdom and the ability to traverse the tree gives still reasons to do what I did, and not having to throw in another lib. If I don't like the way something works, I rewrite 5 lines, instead of having to look for a different lib, and again learn to handle it.

Troy III 272 Posting Pro

you didn't have to write all this code to load objects via xml; Apache commons digester is a library for OXM (object xml mapping). we don't want to reinvent the wheel now do we.

I think we do,
Crockford already did, (think JSON).

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.