001/* 002 * Copyright 2011 Atteo. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014package org.atteo.evo.jaxb; 015 016import java.lang.reflect.Field; 017 018import javax.annotation.Nullable; 019import javax.xml.bind.Binder; 020import javax.xml.bind.annotation.XmlElementWrapper; 021 022import org.w3c.dom.Element; 023import org.w3c.dom.Node; 024import org.w3c.dom.NodeList; 025 026/** 027 * Utility class to iterate over JAXB bindings. 028 */ 029// TODO: add iteration over attributes 030public class JaxbBindings { 031 public interface Runnable { 032 /** 033 * Executed for each found element/object JAXB binding. 034 * @param element DOM element 035 * @param object object which element was unmarshalled to. 036 * @param field field which this element was unmarshalled to, can be null 037 */ 038 void run(Element element, Object object, @Nullable Field field); 039 } 040 041 @SuppressWarnings("rawtypes") 042 private Binder binder; 043 private Runnable runnable; 044 045 @SuppressWarnings("rawtypes") 046 private JaxbBindings(Binder binder, Runnable runnable) { 047 this.binder = binder; 048 this.runnable = runnable; 049 } 050 051 /** 052 * Iterates over pairs consisting of {@link Element} and an object to which it was unmarshalled. 053 * @param root root XML element 054 * @param binder binder with the binding info 055 * @param runnable runnable to run for each {@link Element}/{@link Object} pair. 056 */ 057 @SuppressWarnings("rawtypes") 058 public static void iterate(Element root, Binder binder, Runnable runnable) { 059 @SuppressWarnings("unchecked") 060 Object object = binder.getJAXBNode(root); 061 JaxbBindings bindRecurse = new JaxbBindings(binder, runnable); 062 try { 063 bindRecurse.recurse(root, object, null); 064 } catch (IllegalAccessException e) { 065 throw new RuntimeException(e); 066 } 067 } 068 069 private void recurse(Element element, Object object, @Nullable Field field) 070 throws IllegalAccessException { 071 runnable.run(element, object, field); 072 073 NodeList nodes = element.getChildNodes(); 074 for (int i = 0; i < nodes.getLength(); i++) { 075 Node node = nodes.item(i); 076 if (!(node instanceof Element)) { 077 continue; 078 } 079 Element e = (Element) node; 080 @SuppressWarnings("unchecked") 081 Object child = binder.getJAXBNode(node); 082 Field f; 083 if (child != null) { 084 f = findFieldByValue(object, child); 085 } else { 086 f = findFieldByXmlElementWrapper(object, e.getTagName()); 087 if (f == null) { 088 continue; 089 } 090 child = f.get(object); 091 } 092 recurse(e, child, f); 093 } 094 } 095 096 private static Field findFieldByValue(Object object, Object child) 097 throws IllegalAccessException { 098 Class<?> klass = object.getClass(); 099 while (klass != Object.class) { 100 for (Field field : klass.getDeclaredFields()) { 101 field.setAccessible(true); 102 if (field.get(object) == child) { 103 return field; 104 } 105 } 106 klass = klass.getSuperclass(); 107 } 108 return null; 109 } 110 111 private static Field findFieldByXmlElementWrapper(Object object, String name) 112 throws IllegalAccessException { 113 Class<?> klass = object.getClass(); 114 while (klass != Object.class) { 115 for (Field field : klass.getDeclaredFields()) { 116 field.setAccessible(true); 117 XmlElementWrapper annotation = field.getAnnotation(XmlElementWrapper.class); 118 // TODO: should also check namespace here 119 if (annotation != null && name.equals(annotation.name())) { 120 return field; 121 } 122 } 123 klass = klass.getSuperclass(); 124 } 125 return null; 126 } 127}