blob: 8701d68af3d65adb9c8e36b0b27ff99eedaf9eb0 [file] [log] [blame]
crazybobleec1120632009-02-05 06:23:52 +00001package example.xml;
2
3import com.google.inject.Key;
4import com.google.inject.Module;
5import com.google.inject.Provider;
6import com.google.inject.Binder;
7import java.io.InputStreamReader;
8import java.io.Reader;
9import java.lang.reflect.InvocationTargetException;
10import java.lang.reflect.Method;
11import java.lang.reflect.Type;
12import java.net.URL;
13import java.util.ArrayList;
14import java.util.List;
15import org.xml.sax.Attributes;
16import org.xml.sax.Locator;
17import safesax.Element;
18import safesax.ElementListener;
19import safesax.Parsers;
20import safesax.RootElement;
21import safesax.StartElementListener;
22
23public 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}