001package org.apache.commons.jcs3.utils.config;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.beans.BeanInfo;
023import java.beans.IntrospectionException;
024import java.beans.Introspector;
025import java.beans.PropertyDescriptor;
026import java.io.File;
027import java.lang.reflect.Method;
028import java.util.Properties;
029
030import org.apache.commons.jcs3.log.Log;
031import org.apache.commons.jcs3.log.LogManager;
032
033/**
034 * This class is based on the log4j class org.apache.log4j.config.PropertySetter that was made by
035 * Anders Kristensen
036 * <p>
037 * General purpose Object property setter. Clients repeatedly invokes {@link #setProperty
038 * setProperty(name,value)} in order to invoke setters on the Object specified in the constructor.
039 * This class relies on the JavaBeans {@link Introspector}to analyze the given Object Class using
040 * reflection.
041 * <p>
042 * Usage:
043 *
044 * <pre>
045 * PropertySetter ps = new PropertySetter( anObject );
046 * ps.set( &quot;name&quot;, &quot;Joe&quot; );
047 * ps.set( &quot;age&quot;, &quot;32&quot; );
048 * ps.set( &quot;isMale&quot;, &quot;true&quot; );
049 * </pre>
050 *
051 * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and setMale(true) if
052 * such methods exist with those signatures. Otherwise an {@link IntrospectionException}are thrown.
053 */
054public class PropertySetter
055{
056    /** Logger */
057    private static final Log log = LogManager.getLog( PropertySetter.class );
058
059    /** Description of the Field */
060    private final Object obj;
061
062    /** Description of the Field */
063    private PropertyDescriptor[] props;
064
065    /**
066     * Create a new PropertySetter for the specified Object. This is done in preparation for invoking
067     * {@link #setProperty}one or more times.
068     * @param obj the object for which to set properties
069     */
070    public PropertySetter( final Object obj )
071    {
072        this.obj = obj;
073    }
074
075    /**
076     * Uses JavaBeans {@link Introspector}to compute setters of object to be configured.
077     */
078    protected void introspect()
079    {
080        try
081        {
082            final BeanInfo bi = Introspector.getBeanInfo( obj.getClass() );
083            props = bi.getPropertyDescriptors();
084        }
085        catch ( final IntrospectionException ex )
086        {
087            log.error( "Failed to introspect {0}", obj, ex );
088            props = new PropertyDescriptor[0];
089        }
090    }
091
092    /**
093     * Set the properties of an object passed as a parameter in one go. The <code>properties</code>
094     * are parsed relative to a <code>prefix</code>.
095     * <p>
096     * @param obj The object to configure.
097     * @param properties A java.util.Properties containing keys and values.
098     * @param prefix Only keys having the specified prefix will be set.
099     */
100    public static void setProperties( final Object obj, final Properties properties, final String prefix )
101    {
102        new PropertySetter( obj ).setProperties( properties, prefix );
103    }
104
105    /**
106     * Set the properties for the object that match the <code>prefix</code> passed as parameter.
107     * <p>
108     * @param properties The new properties value
109     * @param prefix The new properties value
110     */
111    public void setProperties( final Properties properties, final String prefix )
112    {
113        final int len = prefix.length();
114
115        for (final String key : properties.stringPropertyNames())
116        {
117            // handle only properties that start with the desired prefix.
118            if ( key.startsWith( prefix ) )
119            {
120                // ignore key if it contains dots after the prefix
121                if ( key.indexOf( '.', len + 1 ) > 0 )
122                {
123                    //System.err.println("----------Ignoring---["+key
124                    //       +"], prefix=["+prefix+"].");
125                    continue;
126                }
127
128                final String value = OptionConverter.findAndSubst( key, properties );
129
130                setProperty( key.substring( len ), value );
131            }
132        }
133
134    }
135
136    /**
137     * Set a property on this PropertySetter's Object. If successful, this method will invoke a
138     * setter method on the underlying Object. The setter is the one for the specified property name
139     * and the value is determined partly from the setter argument type and partly from the value
140     * specified in the call to this method.
141     * <p>
142     * If the setter expects a String no conversion is necessary. If it expects an int, then an
143     * attempt is made to convert 'value' to an int using Integer.valueOf(value). If the setter expects
144     * a boolean, the conversion is by Boolean.valueOf(value).
145     * @param name name of the property
146     * @param value String value of the property
147     */
148
149    public void setProperty( String name, final String value )
150    {
151        if ( value == null )
152        {
153            return;
154        }
155
156        name = Introspector.decapitalize( name );
157        final PropertyDescriptor prop = getPropertyDescriptor( name );
158
159        //log.debug("---------Key: "+name+", type="+prop.getPropertyType());
160
161        if ( prop == null )
162        {
163            log.warn( "No such property [{0}] in {1}.", name, obj.getClass().getName() );
164        }
165        else
166        {
167            try
168            {
169                setProperty( prop, name, value );
170            }
171            catch ( final PropertySetterException ex )
172            {
173                log.warn( "Failed to set property {0} to value \"{1}\".", name, value, ex );
174            }
175        }
176    }
177
178    /**
179     * Set the named property given a {@link PropertyDescriptor}.
180     * @param prop A PropertyDescriptor describing the characteristics of the property to set.
181     * @param name The named of the property to set.
182     * @param value The value of the property.
183     * @throws PropertySetterException
184     */
185
186    public void setProperty( final PropertyDescriptor prop, final String name, final String value )
187        throws PropertySetterException
188    {
189        final Method setter = prop.getWriteMethod();
190        if ( setter == null )
191        {
192            throw new PropertySetterException( "No setter for property" );
193        }
194        final Class<?>[] paramTypes = setter.getParameterTypes();
195        if ( paramTypes.length != 1 )
196        {
197            throw new PropertySetterException( "#params for setter != 1" );
198        }
199
200        final Object arg;
201        try
202        {
203            arg = convertArg( value, paramTypes[0] );
204        }
205        catch ( final Throwable t )
206        {
207            throw new PropertySetterException( "Conversion to type [" + paramTypes[0] + "] failed. Reason: " + t );
208        }
209        if ( arg == null )
210        {
211            throw new PropertySetterException( "Conversion to type [" + paramTypes[0] + "] failed." );
212        }
213        log.debug( "Setting property [{0}] to [{1}].", name, arg );
214        try
215        {
216            setter.invoke( obj, arg );
217        }
218        catch ( final Exception ex )
219        {
220            throw new PropertySetterException( ex );
221        }
222    }
223
224    /**
225     * Convert <code>val</code> a String parameter to an object of a given type.
226     * @param val
227     * @param type
228     * @return Object
229     */
230    protected Object convertArg( final String val, final Class<?> type )
231    {
232        if ( val == null )
233        {
234            return null;
235        }
236
237        final String v = val.trim();
238        if ( String.class.isAssignableFrom( type ) )
239        {
240            return val;
241        }
242        if ( Integer.TYPE.isAssignableFrom( type ) )
243        {
244            return Integer.valueOf( v );
245        }
246        if ( Long.TYPE.isAssignableFrom( type ) )
247        {
248            return Long.valueOf( v );
249        }
250        if ( Boolean.TYPE.isAssignableFrom( type ) )
251        {
252            if ( "true".equalsIgnoreCase( v ) )
253            {
254                return Boolean.TRUE;
255            }
256            if ( "false".equalsIgnoreCase( v ) )
257            {
258                return Boolean.FALSE;
259            }
260        }
261        else if( type.isEnum() )
262        {
263            Enum<?> valueOf = Enum.valueOf(type.asSubclass(Enum.class), v);
264            return valueOf;
265        }
266        else if ( File.class.isAssignableFrom( type ) )
267        {
268            return new File( v );
269        }
270        return null;
271    }
272
273    /**
274     * Gets the propertyDescriptor attribute of the PropertySetter object
275     * @param name
276     * @return The propertyDescriptor value
277     */
278    protected PropertyDescriptor getPropertyDescriptor( final String name )
279    {
280        if ( props == null )
281        {
282            introspect();
283        }
284
285        for (final PropertyDescriptor prop : props)
286        {
287            if ( name.equals( prop.getName() ) )
288            {
289                return prop;
290            }
291        }
292        return null;
293    }
294}