package example.xml; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.Locator; import safesax.Element; import safesax.ElementListener; import safesax.Parsers; import safesax.RootElement; import safesax.StartElementListener; public class XmlBeanModule implements Module { final URL xmlUrl; Locator locator; Binder originalBinder; BeanBuilder beanBuilder; public XmlBeanModule(URL xmlUrl) { this.xmlUrl = xmlUrl; } public void configure(Binder binder) { this.originalBinder = binder; try { RootElement beans = new RootElement("beans"); locator = beans.getLocator(); Element bean = beans.getChild("bean"); bean.setElementListener(new BeanListener()); Element property = bean.getChild("property"); property.setStartElementListener(new PropertyListener()); Reader in = new InputStreamReader(xmlUrl.openStream()); Parsers.parse(in, beans.getContentHandler()); } catch (Exception e) { originalBinder.addError(e); } } /** Handles "binding" elements. */ class BeanListener implements ElementListener { public void start(final Attributes attributes) { Binder sourced = originalBinder.withSource(xmlSource()); String typeString = attributes.getValue("type"); // Make sure 'type' is present. if (typeString == null) { sourced.addError("Missing 'type' attribute."); return; } // Resolve 'type'. Class<?> type; try { type = Class.forName(typeString); } catch (ClassNotFoundException e) { sourced.addError(e); return; } // Look for a no-arg constructor. try { type.getConstructor(); } catch (NoSuchMethodException e) { sourced.addError("%s doesn't have a no-arg constructor."); return; } // Create a bean builder for the given type. beanBuilder = new BeanBuilder(type); } public void end() { if (beanBuilder != null) { beanBuilder.build(); beanBuilder = null; } } } /** Handles "property" elements. */ class PropertyListener implements StartElementListener { public void start(final Attributes attributes) { Binder sourced = originalBinder.withSource(xmlSource()); if (beanBuilder == null) { // We must have already run into an error. return; } // Check for 'name'. String name = attributes.getValue("name"); if (name == null) { sourced.addError("Missing attribute name."); return; } Class<?> type = beanBuilder.type; // Find setter method for the given property name. String setterName = "set" + capitalize(name); Method setter = null; for (Method method : type.getMethods()) { if (method.getName().equals(setterName)) { setter = method; break; } } if (setter == null) { sourced.addError("%s.%s() not found.", type.getName(), setterName); return; } // Validate number of parameters. Type[] parameterTypes = setter.getGenericParameterTypes(); if (parameterTypes.length != 1) { sourced.addError("%s.%s() must take one argument.", setterName, type.getName()); return; } // Add property descriptor to builder. Provider<?> provider = sourced.getProvider(Key.get(parameterTypes[0])); beanBuilder.properties.add(new Property(setter, provider)); } } static String capitalize(String s) { return Character.toUpperCase(s.charAt(0)) + s.substring(1); } static class Property { final Method setter; final Provider<?> provider; Property(Method setter, Provider<?> provider) { this.setter = setter; this.provider = provider; } void setOn(Object o) { try { setter.invoke(o, provider.get()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } } class BeanBuilder { final List<Property> properties = new ArrayList<Property>(); final Class<?> type; BeanBuilder(Class<?> type) { this.type = type; } void build() { addBinding(type); } <T> void addBinding(Class<T> type) { originalBinder .withSource(xmlSource()) .bind(type) .toProvider(new BeanProvider<T>(type, properties)); } } static class BeanProvider<T> implements Provider<T> { final Class<T> type; final List<Property> properties; BeanProvider(Class<T> type, List<Property> properties) { this.type = type; this.properties = properties; } public T get() { try { T t = type.newInstance(); for (Property property : properties) { property.setOn(t); } return t; } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } Object xmlSource() { return xmlUrl + ":" + locator.getLineNumber(); } }