001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.utils.xml;
023
024import java.beans.BeanInfo;
025import java.beans.IntrospectionException;
026import java.beans.Introspector;
027import java.beans.PropertyDescriptor;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.Map;
033
034import org.biojava.bio.Annotation;
035import org.biojava.bio.SmallAnnotation;
036import org.biojava.utils.ChangeVetoException;
037import org.biojava.utils.SmallMap;
038import org.w3c.dom.Element;
039import org.w3c.dom.Node;
040
041/**
042 * Construct java beans from XML elements
043 *
044 * @author Thomas Down
045 */
046
047public class XMLBeans {
048    public final static XMLBeans INSTANCE;
049
050    static {
051        INSTANCE = new XMLBeans();
052    }
053
054    protected XMLBeans() {
055    }
056
057    public Object instantiateBean(Element bel) 
058        throws AppException
059    {
060        return instantiateBean(bel, ClassLoader.getSystemClassLoader(), new HashMap());
061    }
062
063    public Object instantiateBean(Element bel, ClassLoader cloader, Map beanRefs) 
064        throws AppException
065    {
066        String cl = bel.getAttribute("jclass");
067        if (cl == null)
068            throw new AppException("No jclass attribute");
069
070        Object bean = null;
071
072        try {
073            Class clazz = cloader.loadClass(cl);
074            bean = clazz.newInstance();
075            configureBean(bean, bel, beanRefs);
076            if (bean instanceof Initializable)
077                ((Initializable) bean).init();   // FIXME
078        } catch (ClassNotFoundException ex) {
079            throw new AppException("Couldn't load bean class " + cl);
080        } catch (ClassCastException ex) {
081            throw new AppException("Does not implement AppBean: " + cl);
082        } catch (InstantiationException ex) {
083            throw new AppException("Couldn't intantiate bean " + cl);
084        } catch (IllegalAccessException ex) {
085            throw new AppException("Couldn't access constructor for bean " + cl);
086        }
087
088        return bean;
089    }
090
091    private void configureBean(Object bean, Element el, Map refs) 
092        throws AppException
093    {
094        Class clazz = bean.getClass();
095
096        Node child = el.getFirstChild();
097        while (child != null) {
098            if (child instanceof Element) {
099                Element echild = (Element) child;
100                String tag = echild.getTagName();
101                String name = echild.getAttribute("name");
102                Object valueObject = null;
103                Class valueType = null;
104
105                if (tag.equals("string")) {
106                    valueObject = echild.getAttribute("value");
107                    valueType = valueObject.getClass();
108                } else if (tag.equals("bean") || tag.equals("child")) {
109                    // child supported for backwards compatibility.
110
111                    String ref = echild.getAttribute("ref");
112                    Object targ = null;
113                    if (! ref.equals("")) {
114                        targ = refs.get(ref);
115                        if(targ == null) {
116                          throw new NullPointerException(
117                            "Can't find target for: " + ref
118                          );
119                        }
120                    } else {
121                        targ = instantiateBean(echild);
122                    }
123                    
124                    valueObject = targ;
125                    valueType = targ.getClass();
126                } else if (tag.equals("int")) {
127                    String value = echild.getAttribute("value");
128                    try {
129                        int val = Integer.parseInt(value);
130                        valueObject = new Integer(val);
131                        valueType = Integer.TYPE;
132                    } catch (NumberFormatException ex) {
133                        throw new AppException("Invalid int: " + value);
134                    }
135                } else if (tag.equals("double")) {
136                    String value = echild.getAttribute("value");
137                    try {
138                        double val = Double.parseDouble(value);
139                        valueObject = new Double(val);
140                        valueType = Double.TYPE;
141                    } catch (NumberFormatException ex) {
142                        throw new AppException("Invalid double: " + value);
143                    }
144                } else if (tag.equals("boolean")) {
145                    String value = echild.getAttribute("value");
146                    valueObject = new Boolean(value);
147                    valueType = Boolean.TYPE;
148                } else if (tag.equals("set")) {
149                    valueObject = new HashSet();
150                    configureBean(valueObject, echild, refs);
151                    valueType = valueObject.getClass();
152                } else if (tag.equals("list")) {
153                    valueObject = new ArrayList();
154                    configureBean(valueObject, echild, refs);
155                    valueType = valueObject.getClass();
156                } else if (tag.equals("map")) {
157                    valueObject = new SmallMap();
158                    configureBean(valueObject, echild, refs);
159                    valueType = valueObject.getClass();
160                } else if (tag.equals("annotation")) {
161                    valueObject = new SmallAnnotation();
162                    configureBean(valueObject, echild, refs);
163                    valueType = valueObject.getClass();
164                } else {
165                    throw new AppException("Unknown element `" + tag + "' in XML-bean");
166                }
167
168                
169                if (name != null && name.length() > 0) {
170                    setProp(clazz, bean, name, valueObject, valueType);
171                } else {
172                    if (bean instanceof Collection) {
173                        ((Collection) bean).add(valueObject);
174                    } else {
175                        throw new AppException("Anonymous beans are only allowed as children of Collections");
176                    }
177                }
178                
179            }
180            child = child.getNextSibling();
181        }
182    }
183
184    private void setProp(Class clazz, Object bean, String prop, Object value, Class ourType) 
185        throws AppException
186    {
187                BeanInfo bi = null;
188        
189                try {
190                    bi = Introspector.getBeanInfo(clazz);
191                } catch (IntrospectionException ex) {
192                    throw new AppException("Couldn't introspect class " + bean.getClass().getName());
193                }
194                PropertyDescriptor[] descs = bi.getPropertyDescriptors();
195                for (int i = 0; i < descs.length; ++i) {
196                    if (descs[i].getName().equals(prop)) {
197                        PropertyDescriptor desc = descs[i];
198                        if (! desc.getPropertyType().isAssignableFrom(ourType)) {
199                            throw new AppException("Property " + prop + " is not assignable from " + ourType.getName());
200                        }
201                        Object[] obj = new Object[1];
202                        obj[0] = value;
203                        try {
204                            desc.getWriteMethod().invoke(bean, obj);
205                        } catch (Exception ex) {
206                            throw new AppException("Invocation failed, could not invoke " + prop + " " + value);
207                        }
208                        return;
209                    }
210                }
211                if (bean instanceof Map) {
212                    ((Map) bean).put(prop, value);
213                } else if (bean instanceof Annotation) {
214                    try {
215                        ((Annotation) bean).setProperty(prop, value);
216                    } catch (ChangeVetoException cve) {
217                        throw new AppException("Unexpected veto updating Annotation");
218                    }
219                } else {
220                    throw new AppException("Couldn't find property " + prop + " in class " + clazz.getName());
221                }
222    }
223}
224