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 new IllegalArgumentException("Failed to set property: " + pd.getDisplayName(), x);
075            }
076        }
077    }
078
079    public void inject(BeanAccess props, Object bean)
080    {
081        for (PropertyDescriptor pd : getPropertyDescriptors(bean.getClass()))
082        {
083            try
084            {
085                Method method = pd.getReadMethod();
086
087                if ((method != null) && !"class".equals(pd.getName()))
088                {
089                    Object value = method.invoke(bean, (Object[]) null);
090
091                    if (value != null)
092                    {
093                        if (pd.getPropertyType().isArray())
094                        {
095                            for (int i = 0; i < Array.getLength(value); i++)
096                            {
097                                Object v = Array.get(value, i);
098
099                                if ((v != null) && !v.getClass().equals(String.class))
100                                {
101                                    v = v.toString();
102                                }
103
104                                props.propAdd(pd.getName(), (String) v);
105                            }
106                        }
107                        else
108                        {
109                            props.propSet(pd.getName(), value.toString());
110                        }
111                    }
112                }
113            }
114            catch (Exception x)
115            {
116                throw new IllegalArgumentException("Failed to set property: " + pd.getDisplayName(), x);
117            }
118        }
119    }
120
121    @SuppressWarnings("unchecked")
122    public <T> T parse(String value, Class<T> clazz) throws IllegalArgumentException
123    {
124        if (clazz == null)
125        {
126            throw new IllegalArgumentException("null argument");
127        }
128
129        Object o = null;
130
131        if (value == null)
132        {
133            o = zero(clazz);
134        }
135        else if (clazz.isPrimitive())
136        {
137            o = parsePrimitiveValue(value, clazz);
138        }
139        else
140        {
141            if (clazz == String.class)
142            {
143                o = value;
144            }
145            else if (clazz == Character.class)
146            {
147                o = new Character(value.charAt(0));
148            }
149            else
150            {
151                o = parseSpecialValue(value, clazz);
152            }
153        }
154
155        return (T) o;
156    }
157
158    public <T> T proxy(Class<T> clazz, BeanAccess props)
159    {
160        return clazz.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { clazz }, new BeanInvocationHandler(props)));
161    }
162
163    @SuppressWarnings("unchecked")
164    public <T> T zero(Class<T> clazz)
165    {
166        Object o = null;
167
168        if (clazz.isPrimitive())
169        {
170            if (clazz == Boolean.TYPE)
171            {
172                o = Boolean.FALSE;
173            }
174            else if (clazz == Byte.TYPE)
175            {
176                o = Byte.valueOf((byte) 0);
177            }
178            else if (clazz == Character.TYPE)
179            {
180                o = new Character('\0');
181            }
182            else if (clazz == Double.TYPE)
183            {
184                o = new Double(0.0);
185            }
186            else if (clazz == Float.TYPE)
187            {
188                o = new Float(0.0f);
189            }
190            else if (clazz == Integer.TYPE)
191            {
192                o = Integer.valueOf(0);
193            }
194            else if (clazz == Long.TYPE)
195            {
196                o = Long.valueOf(0L);
197            }
198            else if (clazz == Short.TYPE)
199            {
200                o = Short.valueOf((short) 0);
201            }
202        }
203
204        return (T) o;
205    }
206
207    @SuppressWarnings(Warnings.UNCHECKED)
208    protected Object parseSpecialValue(String value, Class clazz) throws IllegalArgumentException
209    {
210        Object o;
211
212        try
213        {
214            if (clazz == File.class)
215            {
216                o = new File(value);
217            }
218            else if (clazz == URL.class)
219            {
220                o = new URL(value);
221            }
222            else if (clazz == URI.class)
223            {
224                o = new URI(value);
225            }
226            else if (clazz == Class.class)
227            {
228                o = Class.forName(value);
229            }
230            else if (clazz == TimeZone.class)
231            {
232                o = TimeZone.getTimeZone(value);
233            }
234            else
235            {
236                // TODO handle constructor with String arg as converter from String
237
238                // look for "valueOf" converter method
239                Method parser = clazz.getMethod(PARSE_METHOD, new Class[] { String.class });
240
241                o = parser.invoke(null, new Object[] { value });
242            }
243        }
244        catch (Exception x)
245        {
246            throw (IllegalArgumentException) new IllegalArgumentException().initCause(x);
247        }
248
249        return o;
250    }
251
252    private PropertyDescriptor[] getPropertyDescriptors(Class clazz)
253    {
254        try
255        {
256            return Introspector.getBeanInfo(clazz).getPropertyDescriptors();
257        }
258        catch (IntrospectionException x)
259        {
260            throw new IllegalArgumentException(x);
261        }
262    }
263
264    private Object parsePrimitiveValue(String value, Class clazz) throws IllegalArgumentException
265    {
266        Object o = null;
267
268        try
269        {
270            if (clazz == Boolean.TYPE)
271            {
272                o = Boolean.valueOf(value);
273            }
274            else if (clazz == Byte.TYPE)
275            {
276                o = Byte.valueOf(value);
277            }
278            else if (clazz == Character.TYPE)
279            {
280                o = new Character(value.charAt(0));
281            }
282            else if (clazz == Double.TYPE)
283            {
284                o = Double.valueOf(value);
285            }
286            else if (clazz == Float.TYPE)
287            {
288                o = Float.valueOf(value);
289            }
290            else if (clazz == Integer.TYPE)
291            {
292                o = Integer.valueOf(value);
293            }
294            else if (clazz == Long.TYPE)
295            {
296                o = Long.valueOf(value);
297            }
298            else if (clazz == Short.TYPE)
299            {
300                o = Short.valueOf(value);
301            }
302        }
303        catch (Exception x)
304        {
305            throw (IllegalArgumentException) new IllegalArgumentException().initCause(x);
306        }
307
308        return o;
309    }
310
311    static class BeanInvocationHandler extends AbstractBeanInvocationHandler
312    {
313        private final BeanAccess _backend;
314
315        BeanInvocationHandler(BeanAccess backend)
316        {
317            _backend = backend;
318        }
319
320        @Override protected Object getPropertySpi(String property, Class<?> clazz)
321        {
322            Object ret = null;
323
324            if (clazz.isArray())
325            {
326                int length = _backend.propLength(property);
327
328                if (length != 0)
329                {
330                    String[] all = new String[length];
331
332                    for (int i = 0; i < all.length; i++)
333                    {
334                        all[i] = _backend.propGet(property, i);
335                    }
336
337                    ret = all;
338                }
339            }
340            else
341            {
342                ret = _backend.propGet(property);
343            }
344
345            return ret;
346        }
347
348        @Override protected void setPropertySpi(String property, Object value, Class<?> clazz)
349        {
350            if (clazz.isArray())
351            {
352                _backend.propDel(property);
353                for (int i = 0; i < Array.getLength(value); i++)
354                {
355                    _backend.propAdd(property, Array.get(value, i).toString());
356                }
357            }
358            else
359            {
360                _backend.propSet(property, value.toString());
361            }
362        }
363
364        @Override protected boolean hasPropertySpi(String property)
365        {
366            return _backend.propLength(property) != 0;
367        }
368    }
369}