View Javadoc
1   package org.apache.commons.jcs3.utils.config;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.beans.BeanInfo;
23  import java.beans.IntrospectionException;
24  import java.beans.Introspector;
25  import java.beans.PropertyDescriptor;
26  import java.io.File;
27  import java.lang.reflect.Method;
28  import java.util.Properties;
29  
30  import org.apache.commons.jcs3.log.Log;
31  import org.apache.commons.jcs3.log.LogManager;
32  
33  /**
34   * This class is based on the log4j class org.apache.log4j.config.PropertySetter that was made by
35   * Anders Kristensen
36   * <p>
37   * General purpose Object property setter. Clients repeatedly invokes {@link #setProperty
38   * setProperty(name,value)} in order to invoke setters on the Object specified in the constructor.
39   * This class relies on the JavaBeans {@link Introspector}to analyze the given Object Class using
40   * reflection.
41   * <p>
42   * Usage:
43   *
44   * <pre>
45   * PropertySetter ps = new PropertySetter( anObject );
46   * ps.set( &quot;name&quot;, &quot;Joe&quot; );
47   * ps.set( &quot;age&quot;, &quot;32&quot; );
48   * ps.set( &quot;isMale&quot;, &quot;true&quot; );
49   * </pre>
50   *
51   * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and setMale(true) if
52   * such methods exist with those signatures. Otherwise an {@link IntrospectionException}are thrown.
53   */
54  public class PropertySetter
55  {
56      /** Logger */
57      private static final Log log = LogManager.getLog( PropertySetter.class );
58  
59      /** Description of the Field */
60      private final Object obj;
61  
62      /** Description of the Field */
63      private PropertyDescriptor[] props;
64  
65      /**
66       * Create a new PropertySetter for the specified Object. This is done in preparation for invoking
67       * {@link #setProperty}one or more times.
68       * @param obj the object for which to set properties
69       */
70      public PropertySetter( final Object obj )
71      {
72          this.obj = obj;
73      }
74  
75      /**
76       * Uses JavaBeans {@link Introspector}to compute setters of object to be configured.
77       */
78      protected void introspect()
79      {
80          try
81          {
82              final BeanInfo bi = Introspector.getBeanInfo( obj.getClass() );
83              props = bi.getPropertyDescriptors();
84          }
85          catch ( final IntrospectionException ex )
86          {
87              log.error( "Failed to introspect {0}", obj, ex );
88              props = new PropertyDescriptor[0];
89          }
90      }
91  
92      /**
93       * Set the properties of an object passed as a parameter in one go. The <code>properties</code>
94       * are parsed relative to a <code>prefix</code>.
95       * <p>
96       * @param obj The object to configure.
97       * @param properties A java.util.Properties containing keys and values.
98       * @param prefix Only keys having the specified prefix will be set.
99       */
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 }