xref: /aosp_15_r20/external/guice/examples/src/example/xml/XmlBeanModule.java (revision dc5640d1ceac12a29404866b9a53df952a7a6c47)
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