001 package com.softnetConsult.utils.attributes; 002 003 import java.io.FileInputStream; 004 import java.io.FileOutputStream; 005 import java.io.IOException; 006 import java.lang.reflect.Array; 007 import java.lang.reflect.Constructor; 008 import java.lang.reflect.Field; 009 import java.lang.reflect.InvocationTargetException; 010 import java.util.ArrayList; 011 import java.util.HashMap; 012 import java.util.Iterator; 013 import java.util.List; 014 import java.util.Map; 015 import java.util.Properties; 016 import java.util.StringTokenizer; 017 018 import com.softnetConsult.utils.collections.ArrayTools; 019 import com.softnetConsult.utils.reflect.ClassTools; 020 021 022 /** 023 * This class allows creating a behaviour similar to {@code java.util.Properties} for 024 * a Java-compilable collection of values; in other words it provides an interface 025 * between the {@code java.util.Properties}-API and a collection of attributes 026 * implemented as a set of public variables of a class. 027 * Assume, a program uses a number of configuration attributes, e.g. {@code width} and 028 * {@code height}. The values could be stored in a common Java properties file and loaded 029 * as needed. However, every time a variable value is needed, a request to the {@code Map} 030 * underlying the {@code Properties} object must be generated. In addition, a conversion 031 * from {@code String} is often necessary (e.g. 032 * {@code int height = Integer.parseString(myProperties.getProperty(“colour”))) }. 033 * This results in an ugly and slow code. Instead, one may have a class 034 * {@code MySettings implements Attributes} that contains two <strong>public static</strong> variables: 035 * {@code width} and {@code height}. AttributeManager provides an API for an <em>automatic</em> 036 * conversion between such a container of public attributes and a {@code Properties} object. 037 * Note that properties congregated in a Properties object are freely accessible (public equivalent). 038 * If full-scale encapsulation to private level is required, a purpose-build class must be created. 039 * 040 * <p style="font-size:smaller;">This product includes software developed by the 041 * <strong>SoftNet-Consult Java Utility Library</strong> project and its contributors.<br /> 042 * (<a href="http://java-tools.sourceforge.net" target="_blank">http://java-tools.sourceforge.net</a>)<br /> 043 * Copyright (c) 2007-2008 SoftNet-Consult.<br /> 044 * Copyright (c) 2007-2008 G. Paperin.<br /> 045 * All rights reserved. 046 * </p> 047 * <p style="font-size:smaller;">File: AttributeManager.java<br /> 048 * Library API version: {@value com.softnetConsult.utils.APIProperties#apiVersion}<br /> 049 * Java compliance version: {@value com.softnetConsult.utils.APIProperties#javaComplianceVersion} 050 * </p> 051 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or 052 * without modification, are permitted provided that the following terms and conditions are met: 053 * </p> 054 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above 055 * acknowledgement of the SoftNet-Consult Java Utility Library project, the above copyright 056 * notice, this list of conditions and the following disclaimer.<br /> 057 * 2. Redistributions in binary form must reproduce the above acknowledgement of the 058 * SoftNet-Consult Java Utility Library project, the above copyright notice, this list of 059 * conditions and the following disclaimer in the documentation and/or other materials 060 * provided with the distribution.<br /> 061 * 3. All advertising materials mentioning features or use of this software or any derived 062 * software must display the following acknowledgement:<br /> 063 * <em>This product includes software developed by the SoftNet-Consult Java Utility Library 064 * project and its contributors.</em> 065 * </p> 066 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY 067 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 068 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 069 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 070 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 071 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 072 * </p> 073 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>) 074 * @version {@value com.softnetConsult.utils.APIProperties#apiVersion} 075 * 076 */ 077 public final class AttributeManager { 078 079 /** 080 * Prevents instances of this class from being created 081 * as this class contains only static utility methods. 082 */ 083 private AttributeManager() {} 084 085 086 /** 087 * Separator used when printing a list of attributes. 088 */ 089 public static final char LIST_SEPARATOR = ';'; 090 091 092 /** 093 * Takes an attribute class object and returns a mapping from the names of variables 094 * defined for the specified class (not object of that class) and their current 095 * (static) values. 096 * 097 * @param attributesClass An attribute class. 098 * @return A mapping from the names of variables defined for the specified class and 099 * their current (static) values represented as Strings. 100 */ 101 public static Map<String, String> getNameValueMap(Class<? extends Attributes> attributesClass) { 102 103 Map<String, String> fieldsValues = new HashMap<String, String>(); 104 105 for (Field field : attributesClass.getFields()) { 106 107 String name = field.getName(); 108 109 Object fval = null; 110 try { 111 fval = field.get(null); 112 } catch(IllegalAccessException e) { 113 fval = "<" + e.toString() + ">"; 114 } 115 116 String value = null; 117 if (null == fval) 118 value = "null"; 119 120 else if (!field.getType().isArray()) 121 value = fval.toString(); 122 123 else // if non-null array: 124 value = ArrayTools.arrayToString(fval, LIST_SEPARATOR); 125 126 fieldsValues.put(name, value); 127 } 128 129 return fieldsValues; 130 } 131 132 /** 133 * Sets the values of the public static variables of an attribute class to the values 134 * loaded from the specified properties file. 135 * 136 * @param attributesClass An attributes collection. 137 * @param fileName A file name. 138 * @return {@code false} if an IOException occured while reading the file, {@code true} otherwise. 139 */ 140 public static boolean load(Class<? extends Attributes> attributesClass, String fileName) { 141 142 // Load from disk: 143 144 Properties props = new Properties(); 145 try { 146 FileInputStream is = new FileInputStream(fileName); 147 try { props.loadFromXML(is); } 148 finally { is.close(); } 149 } catch(IOException e) { 150 e.printStackTrace(); 151 return false; 152 } 153 154 // For all fields of the attributes class: 155 for (Field field : attributesClass.getFields()) { 156 157 // Get field name: 158 String fname = field.getName(); 159 160 // Get loaded field value: 161 String fsval = props.getProperty(fname); 162 if (null == fsval) 163 continue; 164 165 // Get the type of the field: 166 Class<?> fc = field.getType(); 167 168 // If field is not an array: 169 if (!fc.isArray()) { 170 171 // Get warpper for primitive types: 172 if (fc.isPrimitive()) 173 fc = ClassTools.getWrapperClass(fc); 174 175 // Obtain a constructor which takes string: 176 Constructor<?> constr = null; 177 try { 178 constr = fc.getConstructor(String.class); 179 } catch(NoSuchMethodException e) { 180 throw new NoSuchMethodError("The AttributeManager can only initialise fields of a type" 181 + " which has a constructor taking a single String argument;" 182 + " the attribute class \"" + attributesClass.getName() 183 + "\" contains the field \"" + field.getName() + "\" of type \"" 184 + fc.getName() + "\" that does not have such a constructor"); 185 } 186 187 Object val = null; 188 try { 189 val = constr.newInstance(fsval); 190 } catch (InstantiationException ie) { 191 throw new InstantiationError("Problem while constructing field value for " 192 + field.getName() + " was indicated by " + ie); 193 } catch (IllegalAccessException ae) { 194 throw new IllegalAccessError("Problem while constructing field value for " 195 + field.getName() + " was indicated by " + ae); 196 } catch (InvocationTargetException te) { 197 throw new RuntimeException("Problem while constructing field value for " 198 + field.getName() + " was indicated by " + te.getCause()); 199 } 200 201 try { 202 field.set(null, val); 203 } catch (IllegalAccessException e) { 204 throw new IllegalAccessError("Problem while setting field value for " 205 + field.getName() + " was indicated by " + e); 206 } 207 208 // If field IS an array: 209 } else { 210 211 // Get array component type: 212 Class<?> ct = fc.getComponentType(); 213 214 // Strip '[' and ']': 215 if (fsval.length() > 0 && fsval.charAt(0) == '[') 216 fsval = fsval.substring(1); 217 218 if (fsval.length() > 0 && fsval.charAt(fsval.length() - 1) == ']') 219 fsval = fsval.substring(0, fsval.length() - 1); 220 221 // Parse array string: 222 List<String> svals = new ArrayList<String>(); 223 StringTokenizer tok = new StringTokenizer(fsval, new String(new char[] {LIST_SEPARATOR})); 224 while(tok.hasMoreTokens()) { 225 svals.add(tok.nextToken().trim()); 226 } 227 228 // Allocate array: 229 Object arr = Array.newInstance(ct, svals.size()); 230 231 // Get the warapper of the array component type: 232 if (ct.isPrimitive()) 233 ct = ClassTools.getWrapperClass(ct); 234 235 // Obtain a constructor which takes string: 236 Constructor<?> constr = null; 237 try { 238 constr = ct.getConstructor(String.class); 239 } catch(NoSuchMethodException e) { 240 throw new NoSuchMethodError("The AttributeManager can only initialise arrays of a" 241 + " component-type which has a constructor taking a single " 242 + "String argument; the attribute class \"" 243 + attributesClass.getName() + "\" contains the field \"" 244 + field.getName() + "\" of type \"" + fc.getName() 245 + "\" that does not have such a constructor"); 246 } 247 248 // Fill the array: 249 Object aval = null; 250 for(int i = 0; i < svals.size(); i++) { 251 252 try { 253 aval = constr.newInstance(svals.get(i)); 254 } catch (InstantiationException ie) { 255 throw new InstantiationError("Problem while constructing value for element " 256 + i + " of field " + field.getName() 257 + " was indicated by " + ie); 258 } catch (IllegalAccessException ae) { 259 throw new IllegalAccessError("Problem while constructing value for element " 260 + i + " of field " + field.getName() 261 + " was indicated by " + ae); 262 } catch (InvocationTargetException te) { 263 throw new RuntimeException("Problem while constructing value for element " 264 + i + " of field " + field.getName() 265 + " was indicated by " + te.getCause()); 266 } 267 268 Array.set(arr, i, aval); 269 270 } // FOR i=0 TO svals.size() 271 272 try { 273 field.set(null, arr); 274 } catch (IllegalAccessException e) { 275 throw new IllegalAccessError("Problem while setting field value for " 276 + field.getName() + " was indicated by " + e); 277 } 278 279 } // if field IS an array 280 281 } // for (Field field : attributesClass.getFields()) 282 283 return true; 284 } 285 286 /** 287 * Saves the values of static public variables of an attribute collection class to 288 * a Java properties file. 289 * 290 * @param attributesClass An attribute collection. 291 * @param fileName A properties file name. 292 * @return {@code false} if an IOException occured while writing the file, {@code true} otherwise. 293 */ 294 public static boolean save(Class<? extends Attributes> attributesClass, String fileName) { 295 296 Properties props = asProperties(attributesClass); 297 try { 298 FileOutputStream os = new FileOutputStream(fileName); 299 try { props.storeToXML(os, "Attribute values for " + attributesClass.getName() + ".", "UTF-8"); } 300 finally { os.close(); } 301 return true; 302 } catch(IOException e) { 303 e.printStackTrace(); 304 return false; 305 } 306 } 307 308 /** 309 * Converts an attribute collection to a set of properties. 310 * 311 * @param attributesClass An attribute collection class. 312 * @return A set of properties representing the current values of the attributes as Strings. 313 */ 314 public static final Properties asProperties(Class<? extends Attributes> attributesClass) { 315 Map<String, String> fieldsValues = getNameValueMap(attributesClass); 316 Properties props = new Properties(); 317 props.putAll(fieldsValues); 318 return props; 319 } 320 321 /** 322 * Converts an attribute collection to a human readable form. 323 * 324 * @param attributesClass An attribute collection class. 325 * @return An array of strings containing for each attribute of the collection a 326 * string of form {@code name=value}. 327 */ 328 public static final String[] asStrings(Class<? extends Attributes> attributesClass) { 329 330 Map<String, String> fieldsValues = getNameValueMap(attributesClass); 331 String fieldsValuesStrings[] = new String[fieldsValues.size()]; 332 333 Iterator<String> fieldNames = fieldsValues.keySet().iterator(); 334 for (int i = 0; i < fieldsValuesStrings.length; i++) { 335 336 String n = fieldNames.next(); 337 String v = fieldsValues.get(n); 338 fieldsValuesStrings[i] = n + "=" + v; 339 } 340 return fieldsValuesStrings; 341 } 342 343 /** 344 * Converts an attribute collection to a human readable form. 345 * 346 * @param separator Separator between name/value pairs. 347 * @param attributesClass An attribute collection class. 348 * @return A String containing containing for each attribute of the collection a 349 * sub-string of form {@code name=value}, with all such pairs separated by the 350 * specified separator. 351 */ 352 public static final String asString(String separator, Class<? extends Attributes> attributesClass) { 353 354 StringBuffer buff = new StringBuffer(); 355 String[] fieldsStrings = asStrings(attributesClass); 356 357 for (String fs : fieldsStrings) { 358 359 if (buff.length() > 0) 360 buff.append(separator); 361 362 buff.append(fs); 363 } 364 365 return buff.toString(); 366 } 367 368 /** 369 * Converts an attribute collection to a human readable form. 370 * 371 * @param attributesClass An attribute collection class. 372 * @return A String containing containing for each attribute of the collection a 373 * sub-string of form {@code name=value}, with all such pairs separated by the 374 * default separator. 375 */ 376 public static final String asLine(Class<? extends Attributes> attributesClass) { 377 return asString(LIST_SEPARATOR + " ", attributesClass); 378 } 379 380 } // public class AttributeManager