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 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    }