crazyboblee | c112063 | 2009-02-05 06:23:52 +0000 | [diff] [blame] | 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 | } |