/*
* Copyright 2011 Atteo.
*
* 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.atteo.config.jaxb;
import java.lang.reflect.Field;
import javax.annotation.Nullable;
import javax.xml.bind.Binder;
import javax.xml.bind.annotation.XmlElementWrapper;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Utility class to iterate over JAXB bindings.
*/
// TODO: add iteration over attributes
public class JaxbBindings {
public interface Runnable {
/**
* Executed for each found element/object JAXB binding.
* @param element DOM element
* @param object object which element was unmarshalled to.
* @param field field which this element was unmarshalled to, can be null
*/
void run(Element element, Object object, @Nullable Field field);
}
@SuppressWarnings("rawtypes")
private final Binder binder;
private final Runnable runnable;
@SuppressWarnings("rawtypes")
private JaxbBindings(Binder binder, Runnable runnable) {
this.binder = binder;
this.runnable = runnable;
}
/**
* Iterates over pairs consisting of {@link Element} and an object to which it was unmarshalled.
* @param root root XML element
* @param binder binder with the binding info
* @param runnable runnable to run for each {@link Element}/{@link Object} pair.
*/
@SuppressWarnings("rawtypes")
public static void iterate(Element root, Binder binder, Runnable runnable) {
@SuppressWarnings("unchecked")
Object object = binder.getJAXBNode(root);
JaxbBindings bindRecurse = new JaxbBindings(binder, runnable);
try {
bindRecurse.recurse(root, object, null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void recurse(Element element, Object object, @Nullable Field field)
throws IllegalAccessException {
runnable.run(element, object, field);
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (!(node instanceof Element)) {
continue;
}
Element e = (Element) node;
@SuppressWarnings("unchecked")
Object child = binder.getJAXBNode(node);
Field f;
if (child != null) {
f = findFieldByValue(object, child);
} else {
f = findFieldByXmlElementWrapper(object, e.getTagName());
if (f == null) {
continue;
}
child = f.get(object);
}
recurse(e, child, f);
}
}
private static Field findFieldByValue(Object object, Object child)
throws IllegalAccessException {
Class<?> klass = object.getClass();
while (klass != Object.class) {
for (Field field : klass.getDeclaredFields()) {
field.setAccessible(true);
if (field.get(object) == child) {
return field;
}
}
klass = klass.getSuperclass();
}
return null;
}
private static Field findFieldByXmlElementWrapper(Object object, String name)
throws IllegalAccessException {
Class<?> klass = object.getClass();
while (klass != Object.class) {
for (Field field : klass.getDeclaredFields()) {
field.setAccessible(true);
XmlElementWrapper annotation = field.getAnnotation(XmlElementWrapper.class);
// TODO: should also check namespace here
if (annotation != null && name.equals(annotation.name())) {
return field;
}
}
klass = klass.getSuperclass();
}
return null;
}
}