BUG-2288: DOMNotification API
[controller.git] / opendaylight / adsal / northbound / commons / src / main / java / org / opendaylight / controller / northbound / commons / query / TypeInfo.java
1 package org.opendaylight.controller.northbound.commons.query;
2
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;
15 import java.util.Map;
16
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;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * A wrapper over a JAXB type to allow traversal of the object graph and
32  * search for specific values in the object tree.
33  */
34 /*package*/ class TypeInfo {
35
36     public static final Logger LOGGER = LoggerFactory.getLogger(TypeInfo.class);
37     public static final String DEFAULT_NAME = "##default";
38
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;
45     /**
46      * Create a TypeInfo with a name and a class type. The accessor will be null
47      * for a root node.
48      */
49     protected TypeInfo(String name, Class<?> clz, Accessor accessor) {
50         _name = name;
51         _class = clz;
52         _accessor = accessor;
53         XmlAccessorType accessorType = null;
54         if(clz == null) {
55             throw new NullPointerException("Type class can not be null");
56         }
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);
62         }
63     }
64
65     /**
66      * @return the Accessor to access the value
67      */
68     public final Accessor getAccessor() {
69         return _accessor;
70     }
71
72     /**
73      * @return get the child by name
74      */
75     public final TypeInfo getChild(String name) {
76         return _types.get(name);
77     }
78
79     public TypeInfo getCollectionChild(Class<?> childType) {
80         explore();
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)) {
87                     return ti;
88                 }
89             }
90         }
91         return null;
92     }
93
94     public Class getType() {
95         return _class;
96     }
97
98     public String getName() {
99         return _name;
100     }
101
102     /**
103      * @return the object value by a selector query
104      */
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());
109         }
110         if (index >= query.length) return null;
111         explore();
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);
116                 }
117                 // explore the subtype
118                 TypeInfo subTypeInfo = new TypeInfo(getRootName(target.getClass()),
119                         target.getClass(), _accessor);
120                 return subTypeInfo.retrieve(target, query, index);
121             } else {
122                 // non compatible object; bail out
123                 return null;
124             }
125         }
126         TypeInfo child = getChild(query[index]);
127         if (child == null) return null;
128         target = child.getAccessor().getValue(target);
129         if (index+1 == query.length) {
130             // match found
131             return target;
132         }
133         return child.retrieve(target, query, index+1);
134     }
135
136     /**
137      * Explore the type info for children.
138      */
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);
148                 if (tn != null) {
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)));
153                 }
154             }
155             for (Field f : c.getDeclaredFields()) {
156                 String tn = getTypeName(f, _accessType);
157                 if (tn != null) {
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)));
162                 }
163             }
164         }
165         _explored = true;
166     }
167
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 ) {
173             return null;
174         }
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
180             return name;
181         } else if (access == XmlAccessType.FIELD) {
182             // return field name if no annotation present
183             return f.getName();
184         } else if (access == XmlAccessType.PUBLIC_MEMBER
185                 && Modifier.isPublic(f.getModifiers())) { // look for public access
186             return f.getName();
187         }
188         // return annotated name ( if any )
189         return null;
190     }
191
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 ) {
196             return null;
197         }
198         // try to read annotation
199         String name = getTypeName(m.getAnnotations(), m.getName());
200         if (name != null) return name;
201         //check acces type
202         else if (access == XmlAccessType.NONE) { // none return name
203             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);
210         }
211         return null;
212     }
213
214     private static String getBeanPropertyName(Method m){
215         try
216         {
217             Class<?> clazz=m.getDeclaringClass();
218             BeanInfo info = Introspector.getBeanInfo(clazz);
219             PropertyDescriptor[] props = info.getPropertyDescriptors();
220             for (PropertyDescriptor pd : props)
221             {
222                 if (m.equals(pd.getReadMethod())) return pd.getName();
223             }
224         }
225         catch (IntrospectionException e)
226         {
227             LOGGER.error("Could not read bean property name for method = {}",
228                     m.getName(), e);
229         }
230         return null;
231     }
232
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);
240     }
241
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())) {
247             // collection type
248             return new IteratableTypeInfo(name, accessor);
249         }
250         return new TypeInfo(name, accessor.getType(), accessor);
251     }
252
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);
260         }
261         return rootName;
262     }
263
264     protected static String getTypeName(Annotation[] annotations, String dflt) {
265         String name = null;
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
276                 break;
277             } else if (a.annotationType() == XmlType.class) {
278                 name = ((XmlType)a).name();
279             } else if (a.annotationType() == XmlTransient.class) {
280                 // transient type
281                 return null;
282             }
283         }
284         if (DEFAULT_NAME.equals(name)) return dflt;
285         return name;
286     }
287
288     @Override
289     public String toString() {
290         return " TypeInfo [_name=" + _name + ", _class=" + _class
291                 + ", _accessType=" + _accessType + ", _accessor=" + _accessor
292                 + ", _types=" + _types + ", _explored=" + _explored + " ] ";
293     }
294 }