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( "name", "Joe" );
47 * ps.set( "age", "32" );
48 * ps.set( "isMale", "true" );
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 }