/* * Copyright 2010 Chad Retz * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.statmantis.xml; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.Collection; import java.util.List; import java.util.Map; import org.statmantis.BaseRuntimeException; import org.statmantis.annotation.XmlInternalProperty; import org.statmantis.model.Game; import org.statmantis.util.CollectionUtils; import org.statmantis.util.ConvertUtils; import org.statmantis.util.DomUtils; import org.statmantis.util.StringUtils; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.google.common.collect.Maps; /** * Unmarshals XML into Java objects * * @author Chad Retz */ public class XmlUnmarshaller { @SuppressWarnings("unchecked") public <T> T unmarshal(Element element) { Class<T> retClass; try { retClass = (Class<T>) Class.forName( Game.class.getPackage().getName() + "." + StringUtils.capitalize(DomUtils. getElementNameSansNamespace(element))); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } return unmarshal(element, retClass); } @SuppressWarnings("unchecked") public <T> T unmarshal(Element element, Class<T> retClass) { try { //ok, let's loop through the fields and do some reflection work T ret = retClass.newInstance(); //go through the fields and get the xml internal properties Map<String, Field> internalProperties = Maps.newHashMap(); for (Field field : retClass.getDeclaredFields()) { XmlInternalProperty property = field.getAnnotation(XmlInternalProperty.class); if (property != null) { field.setAccessible(true); internalProperties.put(property.value(), field); } } //go through attributes and populate NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); //if there's a prefix, we skip if (node.getNodeName().indexOf(':') != -1) { continue; } try { Field standardField = retClass.getDeclaredField(node.getNodeName()); standardField.setAccessible(true); standardField.set(ret, ConvertUtils.convertKnownType( node.getNodeValue(), standardField.getType())); } catch (NoSuchFieldException e) { //better have an xml internal property Field internalField = internalProperties.get(node.getNodeName()); if (internalField == null) { throw new BaseRuntimeException("Unknown field: " + node.getNodeName()); } Object object = internalField.getType().newInstance(); try { Field toSet = object.getClass().getDeclaredField(node.getNodeName()); toSet.setAccessible(true); toSet.set(object, ConvertUtils.convertKnownType( node.getNodeValue(), toSet.getType())); } catch (NoSuchFieldException e1) { throw new BaseRuntimeException("Unknown field mapping: " + node.getNodeName()); } internalField.set(ret, object); } } //now go through all elements NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (!(child instanceof Element)) { continue; } Element childElement = (Element) child; String childElementName = DomUtils.getElementNameSansNamespace(childElement); //first, we check to see if it's an internal field Field internalField = internalProperties.get(childElementName); if (internalField != null) { //we've got a collection folks... internalField.setAccessible(true); Collection<Object> collection = (Collection<Object>) internalField.get(ret); if (collection == null) { collection = (Collection<Object>) CollectionUtils.newCollectionFromInterface( (Class<Collection<Object>>) internalField.getType()); internalField.set(ret, collection); } Object object = internalField.getType().newInstance(); try { Field toSet = object.getClass().getDeclaredField(childElementName); toSet.setAccessible(true); toSet.set(object, ConvertUtils.convertKnownType( childElement.getTextContent(), toSet.getType())); } catch (NoSuchFieldException e) { throw new BaseRuntimeException("Unknown field mapping: " + childElementName); } collection.add(object); } else { //so it must be a field... try { internalField = retClass.getDeclaredField(childElementName); internalField.setAccessible(true); } catch (Exception e) { throw new BaseRuntimeException("Unknown field mapping: " + childElementName); } //collection? if (!Collection.class.isAssignableFrom(internalField.getType())) { //nope, simple object, so just set it internalField.set(ret, unmarshal(childElement, internalField.getType())); } else { //yup Collection<Object> collection = (Collection<Object>) internalField.get(ret); if (collection == null) { collection = (Collection<Object>) CollectionUtils.newCollectionFromInterface( (Class<Collection<Object>>) internalField.getType()); internalField.set(ret, collection); } //we also need the collection type Class<?> collectionClass = (Class<?>) ((ParameterizedType) internalField. getGenericType()).getActualTypeArguments()[0]; collection.add(unmarshal(childElement, collectionClass)); } } } return ret; } catch (InstantiationException e) { throw new BaseRuntimeException(e); } catch (IllegalAccessException e) { throw new BaseRuntimeException(e); } } public void joinTogetherReferencedObjects(Object subject, List<Object> topLevelPossibles) { //Map<Class<?>, Map<String, Field>> internalProperties = new HashMap<Class<?>, Map<String,Field>>(); throw new UnsupportedOperationException(); } }