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