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