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