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 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 } 51 catch (Exception e) { 52 originalBinder.addError(e); 53 } 54 } 55 56 /** Handles "binding" elements. */ 57 class BeanListener implements ElementListener { 58 start(final Attributes attributes)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 end()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 start(final Attributes attributes)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 capitalize(String s)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 Property(Method setter, Provider<?> provider)160 Property(Method setter, Provider<?> provider) { 161 this.setter = setter; 162 this.provider = provider; 163 } 164 setOn(Object o)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 BeanBuilder(Class<?> type)183 BeanBuilder(Class<?> type) { 184 this.type = type; 185 } 186 build()187 void build() { 188 addBinding(type); 189 } 190 addBinding(Class<T> type)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 BeanProvider(Class<T> type, List<Property> properties)202 BeanProvider(Class<T> type, List<Property> properties) { 203 this.type = type; 204 this.properties = properties; 205 } 206 get()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 xmlSource()224 Object xmlSource() { 225 return xmlUrl + ":" + locator.getLineNumber(); 226 } 227 } 228