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     */
016    package org.ini4j.spi;
017    
018    import java.beans.IntrospectionException;
019    import java.beans.Introspector;
020    import java.beans.PropertyDescriptor;
021    
022    import java.io.File;
023    
024    import java.lang.reflect.Array;
025    import java.lang.reflect.Method;
026    import java.lang.reflect.Proxy;
027    
028    import java.net.URI;
029    import java.net.URL;
030    
031    import java.util.TimeZone;
032    
033    public 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    }