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