001/*
002 * Copyright 2005,2009 Ivan SZKIBA
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.ini4j.spi;
017
018import java.beans.IntrospectionException;
019import java.beans.Introspector;
020import java.beans.PropertyDescriptor;
021
022import java.io.File;
023
024import java.lang.reflect.Array;
025import java.lang.reflect.Method;
026import java.lang.reflect.Proxy;
027
028import java.net.URI;
029import java.net.URL;
030
031import java.util.TimeZone;
032
033public class BeanTool
034{
035    private static final String PARSE_METHOD = "valueOf";
036    private static final BeanTool INSTANCE = ServiceFinder.findService(BeanTool.class);
037
038    public static final BeanTool getInstance()
039    {
040        return INSTANCE;
041    }
042
043    public void inject(Object bean, BeanAccess props)
044    {
045        for (PropertyDescriptor pd : getPropertyDescriptors(bean.getClass()))
046        {
047            try
048            {
049                Method method = pd.getWriteMethod();
050                String name = pd.getName();
051
052                if ((method != null) && (props.propLength(name) != 0))
053                {
054                    Object value;
055
056                    if (pd.getPropertyType().isArray())
057                    {
058                        value = Array.newInstance(pd.getPropertyType().getComponentType(), props.propLength(name));
059                        for (int i = 0; i < props.propLength(name); i++)
060                        {
061                            Array.set(value, i, parse(props.propGet(name, i), pd.getPropertyType().getComponentType()));
062                        }
063                    }
064                    else
065                    {
066                        value = parse(props.propGet(name), pd.getPropertyType());
067                    }
068
069                    method.invoke(bean, value);
070                }
071            }
072            catch (Exception x)
073            {
074                throw (IllegalArgumentException) (new IllegalArgumentException("Failed to set property: " + pd.getDisplayName()).initCause(
075                        x));
076            }
077        }
078    }
079
080    public void inject(BeanAccess props, Object bean)
081    {
082        for (PropertyDescriptor pd : getPropertyDescriptors(bean.getClass()))
083        {
084            try
085            {
086                Method method = pd.getReadMethod();
087
088                if ((method != null) && !"class".equals(pd.getName()))
089                {
090                    Object value = method.invoke(bean, (Object[]) null);
091
092                    if (value != null)
093                    {
094                        if (pd.getPropertyType().isArray())
095                        {
096                            for (int i = 0; i < Array.getLength(value); i++)
097                            {
098                                Object v = Array.get(value, i);
099
100                                if ((v != null) && !v.getClass().equals(String.class))
101                                {
102                                    v = v.toString();
103                                }
104
105                                props.propAdd(pd.getName(), (String) v);
106                            }
107                        }
108                        else
109                        {
110                            props.propSet(pd.getName(), value.toString());
111                        }
112                    }
113                }
114            }
115            catch (Exception x)
116            {
117                throw new IllegalArgumentException("Failed to set property: " + pd.getDisplayName(), x);
118            }
119        }
120    }
121
122    @SuppressWarnings("unchecked")
123    public <T> T parse(String value, Class<T> clazz) throws IllegalArgumentException
124    {
125        if (clazz == null)
126        {
127            throw new IllegalArgumentException("null argument");
128        }
129
130        Object o = null;
131
132        if (value == null)
133        {
134            o = zero(clazz);
135        }
136        else if (clazz.isPrimitive())
137        {
138            o = parsePrimitiveValue(value, clazz);
139        }
140        else
141        {
142            if (clazz == String.class)
143            {
144                o = value;
145            }
146            else if (clazz == Character.class)
147            {
148                o = new Character(value.charAt(0));
149            }
150            else
151            {
152                o = parseSpecialValue(value, clazz);
153            }
154        }
155
156        return (T) o;
157    }
158
159    public <T> T proxy(Class<T> clazz, BeanAccess props)
160    {
161        return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz },
162                    new BeanInvocationHandler(props)));
163    }
164
165    @SuppressWarnings("unchecked")
166    public <T> T zero(Class<T> clazz)
167    {
168        Object o = null;
169
170        if (clazz.isPrimitive())
171        {
172            if (clazz == Boolean.TYPE)
173            {
174                o = Boolean.FALSE;
175            }
176            else if (clazz == Byte.TYPE)
177            {
178                o = Byte.valueOf((byte) 0);
179            }
180            else if (clazz == Character.TYPE)
181            {
182                o = new Character('\0');
183            }
184            else if (clazz == Double.TYPE)
185            {
186                o = new Double(0.0);
187            }
188            else if (clazz == Float.TYPE)
189            {
190                o = new Float(0.0f);
191            }
192            else if (clazz == Integer.TYPE)
193            {
194                o = Integer.valueOf(0);
195            }
196            else if (clazz == Long.TYPE)
197            {
198                o = Long.valueOf(0L);
199            }
200            else if (clazz == Short.TYPE)
201            {
202                o = Short.valueOf((short) 0);
203            }
204        }
205
206        return (T) o;
207    }
208
209    @SuppressWarnings(Warnings.UNCHECKED)
210    protected Object parseSpecialValue(String value, Class clazz) throws IllegalArgumentException
211    {
212        Object o;
213
214        try
215        {
216            if (clazz == File.class)
217            {
218                o = new File(value);
219            }
220            else if (clazz == URL.class)
221            {
222                o = new URL(value);
223            }
224            else if (clazz == URI.class)
225            {
226                o = new URI(value);
227            }
228            else if (clazz == Class.class)
229            {
230                o = Class.forName(value);
231            }
232            else if (clazz == TimeZone.class)
233            {
234                o = TimeZone.getTimeZone(value);
235            }
236            else
237            {
238
239                // TODO handle constructor with String arg as converter from String
240                // look for "valueOf" converter method
241                Method parser = clazz.getMethod(PARSE_METHOD, new Class[] { String.class });
242
243                o = parser.invoke(null, new Object[] { value });
244            }
245        }
246        catch (Exception x)
247        {
248            throw (IllegalArgumentException) new IllegalArgumentException().initCause(x);
249        }
250
251        return o;
252    }
253
254    private PropertyDescriptor[] getPropertyDescriptors(Class clazz)
255    {
256        try
257        {
258            return Introspector.getBeanInfo(clazz).getPropertyDescriptors();
259        }
260        catch (IntrospectionException x)
261        {
262            throw new IllegalArgumentException(x);
263        }
264    }
265
266    private Object parsePrimitiveValue(String value, Class clazz) throws IllegalArgumentException
267    {
268        Object o = null;
269
270        try
271        {
272            if (clazz == Boolean.TYPE)
273            {
274                o = Boolean.valueOf(value);
275            }
276            else if (clazz == Byte.TYPE)
277            {
278                o = Byte.valueOf(value);
279            }
280            else if (clazz == Character.TYPE)
281            {
282                o = new Character(value.charAt(0));
283            }
284            else if (clazz == Double.TYPE)
285            {
286                o = Double.valueOf(value);
287            }
288            else if (clazz == Float.TYPE)
289            {
290                o = Float.valueOf(value);
291            }
292            else if (clazz == Integer.TYPE)
293            {
294                o = Integer.valueOf(value);
295            }
296            else if (clazz == Long.TYPE)
297            {
298                o = Long.valueOf(value);
299            }
300            else if (clazz == Short.TYPE)
301            {
302                o = Short.valueOf(value);
303            }
304        }
305        catch (Exception x)
306        {
307            throw (IllegalArgumentException) new IllegalArgumentException().initCause(x);
308        }
309
310        return o;
311    }
312
313    static class BeanInvocationHandler extends AbstractBeanInvocationHandler
314    {
315        private final BeanAccess _backend;
316
317        BeanInvocationHandler(BeanAccess backend)
318        {
319            _backend = backend;
320        }
321
322        @Override protected Object getPropertySpi(String property, Class<?> clazz)
323        {
324            Object ret = null;
325
326            if (clazz.isArray())
327            {
328                int length = _backend.propLength(property);
329
330                if (length != 0)
331                {
332                    String[] all = new String[length];
333
334                    for (int i = 0; i < all.length; i++)
335                    {
336                        all[i] = _backend.propGet(property, i);
337                    }
338
339                    ret = all;
340                }
341            }
342            else
343            {
344                ret = _backend.propGet(property);
345            }
346
347            return ret;
348        }
349
350        @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
351        {
352            if (clazz.isArray())
353            {
354                _backend.propDel(property);
355                for (int i = 0; i < Array.getLength(value); i++)
356                {
357                    _backend.propAdd(property, Array.get(value, i).toString());
358                }
359            }
360            else
361            {
362                _backend.propSet(property, value.toString());
363            }
364        }
365
366        @Override protected boolean hasPropertySpi(String property)
367        {
368            return _backend.propLength(property) != 0;
369        }
370    }
371}