Home | History | Annotate | Download | only in xml
      1 package example.xml;
      2 
      3 import com.google.inject.Key;
      4 import com.google.inject.Module;
      5 import com.google.inject.Provider;
      6 import com.google.inject.Binder;
      7 import java.io.InputStreamReader;
      8 import java.io.Reader;
      9 import java.lang.reflect.InvocationTargetException;
     10 import java.lang.reflect.Method;
     11 import java.lang.reflect.Type;
     12 import java.net.URL;
     13 import java.util.ArrayList;
     14 import java.util.List;
     15 import org.xml.sax.Attributes;
     16 import org.xml.sax.Locator;
     17 import safesax.Element;
     18 import safesax.ElementListener;
     19 import safesax.Parsers;
     20 import safesax.RootElement;
     21 import safesax.StartElementListener;
     22 
     23 public class XmlBeanModule implements Module {
     24 
     25   final URL xmlUrl;
     26 
     27   Locator locator;
     28   Binder originalBinder;
     29   BeanBuilder beanBuilder;
     30 
     31   public XmlBeanModule(URL xmlUrl) {
     32     this.xmlUrl = xmlUrl;
     33   }
     34 
     35   public void configure(Binder binder) {
     36     this.originalBinder = binder;
     37 
     38     try {
     39       RootElement beans = new RootElement("beans");
     40       locator = beans.getLocator();
     41 
     42       Element bean = beans.getChild("bean");
     43       bean.setElementListener(new BeanListener());
     44 
     45       Element property = bean.getChild("property");
     46       property.setStartElementListener(new PropertyListener());
     47 
     48       Reader in = new InputStreamReader(xmlUrl.openStream());
     49       Parsers.parse(in, beans.getContentHandler());
     50     }
     51     catch (Exception e) {
     52       originalBinder.addError(e);
     53     }
     54   }
     55 
     56   /** Handles "binding" elements. */
     57   class BeanListener implements ElementListener {
     58 
     59     public void start(final Attributes attributes) {
     60       Binder sourced = originalBinder.withSource(xmlSource());
     61 
     62       String typeString = attributes.getValue("type");
     63 
     64       // Make sure 'type' is present.
     65       if (typeString == null) {
     66         sourced.addError("Missing 'type' attribute.");
     67         return;
     68       }
     69 
     70       // Resolve 'type'.
     71       Class<?> type;
     72       try {
     73         type = Class.forName(typeString);
     74       }
     75       catch (ClassNotFoundException e) {
     76         sourced.addError(e);
     77         return;
     78       }
     79 
     80       // Look for a no-arg constructor.
     81       try {
     82         type.getConstructor();
     83       }
     84       catch (NoSuchMethodException e) {
     85         sourced.addError("%s doesn't have a no-arg constructor.");
     86         return;
     87       }
     88 
     89       // Create a bean builder for the given type.
     90       beanBuilder = new BeanBuilder(type);
     91     }
     92 
     93     public void end() {
     94       if (beanBuilder != null) {
     95         beanBuilder.build();
     96         beanBuilder = null;
     97       }
     98     }
     99   }
    100 
    101   /** Handles "property" elements. */
    102   class PropertyListener implements StartElementListener {
    103 
    104     public void start(final Attributes attributes) {
    105       Binder sourced = originalBinder.withSource(xmlSource());
    106 
    107       if (beanBuilder == null) {
    108         // We must have already run into an error.
    109         return;
    110       }
    111 
    112       // Check for 'name'.
    113       String name = attributes.getValue("name");
    114       if (name == null) {
    115         sourced.addError("Missing attribute name.");
    116         return;
    117       }
    118 
    119       Class<?> type = beanBuilder.type;
    120 
    121       // Find setter method for the given property name.
    122       String setterName = "set" + capitalize(name);
    123       Method setter = null;
    124       for (Method method : type.getMethods()) {
    125         if (method.getName().equals(setterName)) {
    126           setter = method;
    127           break;
    128         }
    129       }
    130       if (setter == null) {
    131         sourced.addError("%s.%s() not found.", type.getName(), setterName);
    132         return;
    133       }
    134 
    135       // Validate number of parameters.
    136       Type[] parameterTypes = setter.getGenericParameterTypes();
    137       if (parameterTypes.length != 1) {
    138         sourced.addError("%s.%s() must take one argument.",
    139             setterName, type.getName());
    140         return;
    141       }
    142 
    143       // Add property descriptor to builder.
    144       Provider<?> provider = sourced.getProvider(Key.get(parameterTypes[0]));
    145       beanBuilder.properties.add(
    146           new Property(setter, provider));
    147     }
    148   }
    149 
    150   static String capitalize(String s) {
    151     return Character.toUpperCase(s.charAt(0)) +
    152         s.substring(1);
    153   }
    154 
    155   static class Property {
    156 
    157     final Method setter;
    158     final Provider<?> provider;
    159 
    160     Property(Method setter, Provider<?> provider) {
    161       this.setter = setter;
    162       this.provider = provider;
    163     }
    164 
    165     void setOn(Object o) {
    166       try {
    167         setter.invoke(o, provider.get());
    168       }
    169       catch (IllegalAccessException e) {
    170         throw new RuntimeException(e);
    171       }
    172       catch (InvocationTargetException e) {
    173         throw new RuntimeException(e);
    174       }
    175     }
    176   }
    177 
    178   class BeanBuilder {
    179 
    180     final List<Property> properties = new ArrayList<Property>();
    181     final Class<?> type;
    182 
    183     BeanBuilder(Class<?> type) {
    184       this.type = type;
    185     }
    186 
    187     void build() {
    188       addBinding(type);
    189     }
    190 
    191     <T> void addBinding(Class<T> type) {
    192       originalBinder.withSource(xmlSource())
    193           .bind(type).toProvider(new BeanProvider<T>(type, properties));
    194     }
    195   }
    196 
    197   static class BeanProvider<T> implements Provider<T> {
    198 
    199     final Class<T> type;
    200     final List<Property> properties;
    201 
    202     BeanProvider(Class<T> type, List<Property> properties) {
    203       this.type = type;
    204       this.properties = properties;
    205     }
    206 
    207     public T get() {
    208       try {
    209         T t = type.newInstance();
    210         for (Property property : properties) {
    211           property.setOn(t);
    212         }
    213         return t;
    214       }
    215       catch (InstantiationException e) {
    216         throw new RuntimeException(e);
    217       }
    218       catch (IllegalAccessException e) {
    219         throw new RuntimeException(e);
    220       }
    221     }
    222   }
    223 
    224   Object xmlSource() {
    225     return xmlUrl + ":" + locator.getLineNumber();
    226   }
    227 }
    228