1 package org.opendaylight.controller.northbound.commons.query;
3 import java.beans.BeanInfo;
4 import java.beans.IntrospectionException;
5 import java.beans.Introspector;
6 import java.beans.PropertyDescriptor;
7 import java.lang.annotation.Annotation;
8 import java.lang.reflect.Field;
9 import java.lang.reflect.Method;
10 import java.lang.reflect.Modifier;
11 import java.lang.reflect.ParameterizedType;
12 import java.lang.reflect.Type;
13 import java.util.Collection;
14 import java.util.HashMap;
17 import javax.xml.bind.annotation.XmlAccessType;
18 import javax.xml.bind.annotation.XmlAccessorType;
19 import javax.xml.bind.annotation.XmlAttribute;
20 import javax.xml.bind.annotation.XmlElement;
21 import javax.xml.bind.annotation.XmlElementRef;
22 import javax.xml.bind.annotation.XmlElementWrapper;
23 import javax.xml.bind.annotation.XmlRootElement;
24 import javax.xml.bind.annotation.XmlTransient;
25 import javax.xml.bind.annotation.XmlType;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * A wrapper over a JAXB type to allow traversal of the object graph and
32 * search for specific values in the object tree.
34 /*package*/ class TypeInfo {
36 public static final Logger LOGGER = LoggerFactory.getLogger(TypeInfo.class);
37 public static final String DEFAULT_NAME = "##default";
39 protected final String _name; // the jaxb name
40 protected Class<?> _class; // jaxb type class
41 protected final XmlAccessType _accessType; // jaxb access type
42 protected final Accessor _accessor; // accessor to access object value
43 protected Map<String,TypeInfo> _types = new HashMap<String,TypeInfo>();
44 protected volatile boolean _explored = false;
46 * Create a TypeInfo with a name and a class type. The accessor will be null
49 protected TypeInfo(String name, Class<?> clz, Accessor accessor) {
53 XmlAccessorType accessorType = null;
55 throw new NullPointerException("Type class can not be null");
57 accessorType = clz.getAnnotation(XmlAccessorType.class);
58 _accessType = (accessorType == null ?
59 XmlAccessType.PUBLIC_MEMBER : accessorType.value());
60 if (LOGGER.isDebugEnabled()) {
61 LOGGER.debug("Created type info name:{} type:{}", _name, _class);
66 * @return the Accessor to access the value
68 public final Accessor getAccessor() {
73 * @return get the child by name
75 public final TypeInfo getChild(String name) {
76 return _types.get(name);
79 public TypeInfo getCollectionChild(Class<?> childType) {
81 for (TypeInfo ti : _types.values()) {
82 if (Collection.class.isAssignableFrom(ti.getType())) {
83 ParameterizedType p = (ParameterizedType)
84 ti.getAccessor().getGenericType();
85 Type[] pts = p.getActualTypeArguments();
86 if (pts.length == 1 && pts[0].equals(childType)) {
94 public Class getType() {
98 public String getName() {
103 * @return the object value by a selector query
105 public Object retrieve(Object target, String[] query, int index)
106 throws QueryException {
107 if (LOGGER.isDebugEnabled()) {
108 LOGGER.debug("retrieve: {}/{} type:{}", index, query.length, target.getClass());
110 if (index >= query.length) return null;
112 if (!target.getClass().equals(_class)) {
113 if (_class.isAssignableFrom(target.getClass())) {
114 if (LOGGER.isDebugEnabled()) {
115 LOGGER.debug("Handling subtype {} of {} ", target.getClass(), _class);
117 // explore the subtype
118 TypeInfo subTypeInfo = new TypeInfo(getRootName(target.getClass()),
119 target.getClass(), _accessor);
120 return subTypeInfo.retrieve(target, query, index);
122 // non compatible object; bail out
126 TypeInfo child = getChild(query[index]);
127 if (child == null) return null;
128 target = child.getAccessor().getValue(target);
129 if (index+1 == query.length) {
133 return child.retrieve(target, query, index+1);
137 * Explore the type info for children.
139 public synchronized void explore() {
140 if (_explored) return;
141 for (Class<?> c = _class; c != null; c = c.getSuperclass()) {
142 if (c.equals(Object.class)) break;
143 // Currently only fields and methods annotated with JAXB annotations are
144 // considered as valid for search purposes.
145 //check methods first
146 for (Method m : c.getDeclaredMethods()) {
147 String tn = getTypeName(m, _accessType);
149 if (LOGGER.isDebugEnabled()) LOGGER.debug(
150 "exploring type: {} name: {} method: {}",
151 _class.getSimpleName(), tn, m);
152 _types.put(tn, createTypeInfo(tn, new Accessor(m)));
155 for (Field f : c.getDeclaredFields()) {
156 String tn = getTypeName(f, _accessType);
158 if (LOGGER.isDebugEnabled()) LOGGER.debug(
159 "exploring type: {} name: {} field: {}",
160 _class.getSimpleName(), tn, f);
161 _types.put(tn, createTypeInfo(tn, new Accessor(f)));
168 public static final String getTypeName(Field f, XmlAccessType access) {
169 // ignore static, transient and xmltransient fields
170 if (Modifier.isTransient(f.getModifiers()) ||
171 Modifier.isStatic(f.getModifiers()) ||
172 f.getAnnotation(XmlTransient.class) != null ) {
175 // try to read annotation
176 String name = getTypeName(f.getAnnotations(), f.getName());
177 if (name != null) return name;
178 // no annotation present check accesstype
179 else if (access == XmlAccessType.NONE) { // none return name
181 } else if (access == XmlAccessType.FIELD) {
182 // return field name if no annotation present
184 } else if (access == XmlAccessType.PUBLIC_MEMBER
185 && Modifier.isPublic(f.getModifiers())) { // look for public access
188 // return annotated name ( if any )
192 public static final String getTypeName(Method m, XmlAccessType access) {
193 // ignore static, transient and xmltransient fields
194 if (Modifier.isStatic(m.getModifiers()) ||
195 m.getAnnotation(XmlTransient.class) != null ) {
198 // try to read annotation
199 String name = getTypeName(m.getAnnotations(), m.getName());
200 if (name != null) return name;
202 else if (access == XmlAccessType.NONE) { // none return name
204 } else if (access == XmlAccessType.PROPERTY) {
205 // return bean property name if no annotation present
206 return getBeanPropertyName(m);
207 } else if (access == XmlAccessType.PUBLIC_MEMBER
208 && Modifier.isPublic(m.getModifiers())) { // look for public access
209 return getBeanPropertyName(m);
214 private static String getBeanPropertyName(Method m){
217 Class<?> clazz=m.getDeclaringClass();
218 BeanInfo info = Introspector.getBeanInfo(clazz);
219 PropertyDescriptor[] props = info.getPropertyDescriptors();
220 for (PropertyDescriptor pd : props)
222 if (m.equals(pd.getReadMethod())) return pd.getName();
225 catch (IntrospectionException e)
227 LOGGER.error("Could not read bean property name for method = {}",
233 public static TypeInfo createRoot(String name, Class<?> clz) {
234 // root is always a composite type
235 // FIXME assert its a JAXB type
236 XmlRootElement root = clz.getAnnotation(XmlRootElement.class);
237 if (root == null) throw new IllegalArgumentException("Not a JAXB type: " + clz);
238 if (name == null) name = getRootName(clz);
239 return new TypeInfo(name, clz, null);
242 public static TypeInfo createTypeInfo(String name, Accessor accessor) {
243 if (accessor.getAccessibleObject().getAnnotation(XmlElementWrapper.class) != null) {
244 //XmlElementWrapperType
245 return new WrapperTypeInfo(name, accessor);
246 } else if (Collection.class.isAssignableFrom(accessor.getType())) {
248 return new IteratableTypeInfo(name, accessor);
250 return new TypeInfo(name, accessor.getType(), accessor);
253 public static String getRootName(Class<?> cls) {
254 XmlRootElement root = cls.getAnnotation(XmlRootElement.class);
255 if (root == null) return null;
256 String rootName = root.name();
257 if (DEFAULT_NAME.equals(rootName)) {
258 String clsName = cls.getSimpleName();
259 rootName = Character.toLowerCase(clsName.charAt(0)) + clsName.substring(1);
264 protected static String getTypeName(Annotation[] annotations, String dflt) {
266 for (Annotation a : annotations) {
267 if (a.annotationType() == XmlAttribute.class) {
268 name = ((XmlAttribute)a).name();
269 } else if (a.annotationType() == XmlElement.class) {
270 name = ((XmlElement)a).name();
271 } else if (a.annotationType() == XmlElementRef.class) {
272 name = ((XmlElementRef)a).name();
273 } else if (a.annotationType() == XmlElementWrapper.class) {
274 name = ((XmlElementWrapper)a).name();
275 // break the loop as we don't want name to be overwritten by XmlElement
277 } else if (a.annotationType() == XmlType.class) {
278 name = ((XmlType)a).name();
279 } else if (a.annotationType() == XmlTransient.class) {
284 if (DEFAULT_NAME.equals(name)) return dflt;
289 public String toString() {
290 return " TypeInfo [_name=" + _name + ", _class=" + _class
291 + ", _accessType=" + _accessType + ", _accessor=" + _accessor
292 + ", _types=" + _types + ", _explored=" + _explored + " ] ";