001: /*
002: * $Id: PropertyReaderUtils.java,v 1.59 2007/09/13 11:31:30 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.common.collections;
008:
009: import java.io.InputStream;
010: import java.io.IOException;
011:
012: import java.util.Enumeration;
013: import java.util.Iterator;
014: import java.util.Properties;
015:
016: import org.xins.common.MandatoryArgumentChecker;
017:
018: import org.xins.common.text.FastStringBuffer;
019: import org.xins.common.text.TextUtils;
020: import org.xins.common.text.URLEncoding;
021:
022: /**
023: * Utility functions for dealing with <code>PropertyReader</code> objects.
024: *
025: * @version $Revision: 1.59 $ $Date: 2007/09/13 11:31:30 $
026: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
027: *
028: * @since XINS 1.0.0
029: *
030: * @see PropertyReader
031: */
032: public final class PropertyReaderUtils {
033:
034: /**
035: * An empty and unmodifiable <code>PropertyReader</code> instance. This
036: * field is not <code>null</code>.
037: *
038: * @since XINS 1.1.0
039: */
040: public static final PropertyReader EMPTY_PROPERTY_READER = new ProtectedPropertyReader(
041: new Object());
042:
043: /**
044: * Secret key object used when dealing with
045: * <code>ProtectedPropertyReader</code> instances.
046: */
047: private static final Object SECRET_KEY = new Object();
048:
049: /**
050: * Constructs a new <code>PropertyReaderUtils</code> object. This
051: * constructor is marked as <code>private</code>, since no objects of this
052: * class should be constructed.
053: */
054: private PropertyReaderUtils() {
055: // empty
056: }
057:
058: /**
059: * Gets the property with the specified name and converts it to a
060: * <code>boolean</code>.
061: *
062: * @param properties
063: * the set of properties to read from, cannot be <code>null</code>.
064: *
065: * @param propertyName
066: * the name of the property to read, cannot be <code>null</code>.
067: *
068: * @param fallbackDefault
069: * the fallback default value, returned if the value of the property is
070: * either <code>null</code> or <code>""</code> (an empty string).
071: *
072: * @return
073: * the value of the property.
074: *
075: * @throws IllegalArgumentException
076: * if <code>properties == null || propertyName == null</code>.
077: *
078: * @throws InvalidPropertyValueException
079: * if the value of the property is neither <code>null</code> nor
080: * <code>""</code> (an empty string), nor <code>"true"</code> nor
081: * <code>"false"</code>.
082: */
083: public static boolean getBooleanProperty(PropertyReader properties,
084: String propertyName, boolean fallbackDefault)
085: throws IllegalArgumentException,
086: InvalidPropertyValueException {
087:
088: // Check preconditions
089: MandatoryArgumentChecker.check("properties", properties,
090: "propertyName", propertyName);
091:
092: // Query the PropertyReader
093: String value = properties.get(propertyName);
094:
095: // Fallback to the default, if necessary
096: if (TextUtils.isEmpty(value)) {
097: return fallbackDefault;
098: }
099:
100: // Parse the string
101: if ("true".equals(value)) {
102: return true;
103: } else if ("false".equals(value)) {
104: return false;
105: } else {
106: throw new InvalidPropertyValueException(propertyName, value);
107: }
108: }
109:
110: /**
111: * Gets the property with the specified name and converts it to an
112: * <code>int</code>.
113: *
114: * @param properties
115: * the set of properties to read from, cannot be <code>null</code>.
116: *
117: * @param propertyName
118: * the name of the property to read, cannot be <code>null</code>.
119: *
120: * @return
121: * the value of the property, as an <code>int</code>.
122: *
123: * @throws IllegalArgumentException
124: * if <code>properties == null || propertyName == null</code>.
125: *
126: * @throws MissingRequiredPropertyException
127: * if the specified property is not set, or if it is set to an empty
128: * string.
129: *
130: * @throws InvalidPropertyValueException
131: * if the conversion to an <code>int</code> failed.
132: */
133: public static int getIntProperty(PropertyReader properties,
134: String propertyName) throws IllegalArgumentException,
135: MissingRequiredPropertyException,
136: InvalidPropertyValueException {
137:
138: // Check preconditions
139: MandatoryArgumentChecker.check("properties", properties,
140: "propertyName", propertyName);
141:
142: // Query the PropertyReader
143: String value = properties.get(propertyName);
144:
145: // Make sure the value is set
146: if (value == null || value.length() == 0) {
147: throw new MissingRequiredPropertyException(propertyName);
148: }
149:
150: // Parse the string
151: try {
152: return Integer.parseInt(value);
153: } catch (NumberFormatException exception) {
154: throw new InvalidPropertyValueException(propertyName, value);
155: }
156: }
157:
158: /**
159: * Retrieves the specified property and throws a
160: * <code>MissingRequiredPropertyException</code> if it is not set.
161: *
162: * @param properties
163: * the set of properties to retrieve a specific proeprty from, cannot be
164: * <code>null</code>.
165: *
166: * @param name
167: * the name of the property, cannot be <code>null</code>.
168: *
169: * @return
170: * the value of the property, guaranteed not to be <code>null</code> and
171: * guaranteed to contain at least one character.
172: *
173: * @throws IllegalArgumentException
174: * if <code>properties == null || name == null</code>.
175: *
176: * @throws MissingRequiredPropertyException
177: * if the value of the property is either <code>null</code> or an empty
178: * string.
179: */
180: public static String getRequiredProperty(PropertyReader properties,
181: String name) throws IllegalArgumentException,
182: MissingRequiredPropertyException {
183:
184: // Check preconditions
185: MandatoryArgumentChecker.check("properties", properties,
186: "name", name);
187:
188: // Retrieve the value
189: String value = properties.get(name);
190:
191: // The property is required
192: if (value == null || value.length() < 1) {
193: throw new MissingRequiredPropertyException(name);
194: }
195:
196: return value;
197: }
198:
199: /**
200: * Retrieves a property with the specified name, falling back to a default
201: * value if the property is not set.
202: *
203: * @param properties
204: * the set of properties to retrieve a property from,
205: * cannot be <code>null</code>.
206: *
207: * @param key
208: * the property key,
209: * cannot be <code>null</code>.
210: *
211: * @param fallbackValue
212: * the fallback default value, returned in case the property is not set
213: * in <code>properties</code>, cannot be <code>null</code>.
214: *
215: * @return
216: * the value of the property or the fallback value.
217: *
218: * @throws IllegalArgumentException
219: * if <code>properties == null || key == null || fallbackValue == null</code>.
220: *
221: * @since XINS 2.1
222: */
223: public String getWithDefault(PropertyReader properties, String key,
224: String fallbackValue) throws IllegalArgumentException {
225:
226: // Check preconditions
227: MandatoryArgumentChecker.check("properties", properties, "key",
228: key, "fallbackValue", fallbackValue);
229:
230: // Get value
231: String value = properties.get(key);
232: if (value != null) {
233: return value;
234:
235: // Fallback if necessary
236: } else {
237: return fallbackValue;
238: }
239: }
240:
241: /**
242: * Constructs a <code>PropertyReader</code> from the specified input
243: * stream.
244: *
245: * <p>The parsing done is similar to the parsing done by the
246: * {@link Properties#load(InputStream)} method. Empty values will be
247: * ignored.
248: *
249: * @param in
250: * the input stream to read from, cannot be <code>null</code>.
251: *
252: * @return
253: * a {@link PropertyReader} instance that contains all the properties
254: * defined in the specified input stream.
255: *
256: * @throws IllegalArgumentException
257: * if <code>in == null</code>.
258: *
259: * @throws IOException
260: * if there was an I/O error while reading from the stream.
261: */
262: public static PropertyReader createPropertyReader(InputStream in)
263: throws IllegalArgumentException, IOException {
264:
265: // Check preconditions
266: MandatoryArgumentChecker.check("in", in);
267:
268: // Parse the input stream using java.util.Properties
269: Properties properties = new Properties();
270: properties.load(in);
271:
272: // Convert from java.util.Properties to PropertyReader
273: ProtectedPropertyReader r = new ProtectedPropertyReader(
274: SECRET_KEY);
275: Enumeration names = properties.propertyNames();
276: while (names.hasMoreElements()) {
277: String key = (String) names.nextElement();
278: String value = properties.getProperty(key);
279:
280: if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
281: r.set(SECRET_KEY, key, value);
282: }
283: }
284:
285: return r;
286: }
287:
288: /**
289: * Serializes the specified <code>PropertyReader</code> to a
290: * <code>FastStringBuffer</code>. For each entry, both the key and the
291: * value are encoded using the URL encoding (see {@link URLEncoding}).
292: * The key and value are separated by a literal equals sign
293: * (<code>'='</code>). The entries are separated using
294: * an ampersand (<code>'&'</code>).
295: *
296: * <p>If the value for an entry is either <code>null</code> or an empty
297: * string (<code>""</code>), then nothing is added to the buffer for that
298: * entry.
299: *
300: * @param properties
301: * the {@link PropertyReader} to serialize, can be <code>null</code>.
302: *
303: * @param buffer
304: * the buffer to write the serialized data to, cannot be
305: * <code>null</code>.
306: *
307: * @param valueIfEmpty
308: * the string to append to the buffer in case
309: * <code>properties == null || properties.size() == 0</code>; if this
310: * argument is <code>null</code>, however, then nothing will be appended
311: * in the mentioned case.
312: *
313: * @throws IllegalArgumentException
314: * if <code>properties == null || buffer == null</code>.
315: *
316: * @deprecated since XINS 2.0, use {@link #toString(PropertyReader, String)}
317: */
318: public static void serialize(PropertyReader properties,
319: FastStringBuffer buffer, String valueIfEmpty)
320: throws IllegalArgumentException {
321:
322: // Check preconditions
323: MandatoryArgumentChecker.check("buffer", buffer);
324:
325: // Catch special case: No properties available.
326: if (properties == null || properties.size() == 0) {
327: if (valueIfEmpty != null) {
328: buffer.append(valueIfEmpty);
329: }
330: return;
331: }
332:
333: // Loop over all properties
334: Iterator names = properties.getNames();
335: boolean first = true;
336: while (names.hasNext()) {
337:
338: // Get the name and value
339: String name = (String) names.next();
340: String value = properties.get(name);
341:
342: // If the value is null or an empty string, then output nothing
343: if (value == null) {
344: continue;
345: }
346:
347: // Append an ampersand, except for the first entry
348: if (!first) {
349: buffer.append('&');
350: } else {
351: first = false;
352: }
353:
354: // Append the key and the value, separated by an equals sign
355: buffer.append(URLEncoding.encode(name));
356: buffer.append('=');
357: buffer.append(URLEncoding.encode(value));
358: }
359: }
360:
361: /**
362: * Returns the String representation of the specified <code>PropertyReader</code>.
363: * For each entry, both the key and the value are encoded using the URL
364: * encoding (see {@link URLEncoding}).
365: * The key and value are separated by a literal equals sign
366: * (<code>'='</code>). The entries are separated using an ampersand
367: * (<code>'&'</code>).
368: *
369: * <p>If the value for an entry is either <code>null</code> or an empty
370: * string (<code>""</code>), then nothing is added to the String for that
371: * entry.
372: *
373: * @param properties
374: * the {@link PropertyReader} to serialize, cannot be <code>null</code>.
375: *
376: * @return
377: * the String representation of the specified <code>PropertyReader</code>.
378: *
379: * @since XINS 2.0.
380: */
381: public static String toString(PropertyReader properties) {
382: return toString(properties, null, null, null, -1);
383: }
384:
385: /**
386: * Serializes the specified <code>PropertyReader</code> to a
387: * <code>String</code>. For each entry, both the key and the
388: * value are encoded using the URL encoding (see {@link URLEncoding}).
389: * The key and value are separated by a literal equals sign
390: * (<code>'='</code>). The entries are separated using
391: * an ampersand (<code>'&'</code>).
392: *
393: * <p>If the value for an entry is either <code>null</code> or an empty
394: * string (<code>""</code>), then nothing is added to the String for that
395: * entry.
396: *
397: * @param properties
398: * the {@link PropertyReader} to serialize, can be <code>null</code>.
399: *
400: * @param valueIfEmpty
401: * the string to append to the buffer in case
402: * <code>properties == null || properties.size() == 0</code>.
403: *
404: * @return
405: * the String representation of the PropertyReader or the valueIfEmpty, never <code>null</code>.
406: * If all parameters are <code>null</code> then an empty String is returned.
407: */
408: public static String toString(PropertyReader properties,
409: String valueIfEmpty) {
410: return toString(properties, valueIfEmpty, null, null, -1);
411: }
412:
413: /**
414: * Returns the <code>String</code> representation for the specified
415: * <code>PropertyReader</code>.
416: *
417: * @param properties
418: * the {@link PropertyReader} to construct a String for, or <code>null</code>.
419: *
420: * @param valueIfEmpty
421: * the value to return if the specified set of properties is either
422: * <code>null</code> or empty, can be <code>null</code>.
423: *
424: * @param prefixIfNotEmpty
425: * the prefix to add to the value if the <code>PropertyReader</code>
426: * is not empty, can be <code>null</code>.
427: *
428: * @param suffix
429: * the suffix to add to the value, can be <code>null</code>. The suffix
430: * will be added even if the PropertyReaderis empty.
431: *
432: * @return
433: * the String representation of the PropertyReader with the different artifacts, never <code>null</code>.
434: * If all parameters are <code>null</code> then an empty String is returned.
435: *
436: * @since XINS 2.0
437: */
438: public static String toString(PropertyReader properties,
439: String valueIfEmpty, String prefixIfNotEmpty, String suffix) {
440: return toString(properties, valueIfEmpty, prefixIfNotEmpty,
441: suffix, -1);
442: }
443:
444: /**
445: * Returns the <code>String</code> representation for the specified
446: * <code>PropertyReader</code>.
447: *
448: * @param properties
449: * the {@link PropertyReader} to construct a String for, or <code>null</code>.
450: *
451: * @param valueIfEmpty
452: * the value to return if the specified set of properties is either
453: * <code>null</code> or empty, can be <code>null</code>.
454: *
455: * @param prefixIfNotEmpty
456: * the prefix to add to the value if the <code>PropertyReader</code>
457: * is not empty, can be <code>null</code>.
458: *
459: * @param suffix
460: * the suffix to add to the value, can be <code>null</code>. The suffix
461: * will be added even if the PropertyReaderis empty.
462: *
463: * @param maxValueLength
464: * the maximum of characters to set for the value, if the value is longer
465: * than this limit '...' will be added after the limit.
466: * If the value is -1, no limit will be set.
467: *
468: * @return
469: * the String representation of the PropertyReader with the different artifacts, never <code>null</code>.
470: * If all parameters are <code>null</code> then an empty String is returned.
471: *
472: * @since XINS 2.0
473: */
474: public static String toString(PropertyReader properties,
475: String valueIfEmpty, String prefixIfNotEmpty,
476: String suffix, int maxValueLength) {
477:
478: // If the property set if null, return the fallback
479: if (properties == null) {
480: if (suffix != null) {
481: return suffix;
482: } else {
483: return valueIfEmpty;
484: }
485: }
486:
487: Iterator names = properties.getNames();
488:
489: // If there are no parameters, then return the fallback
490: if (!names.hasNext()) {
491: if (suffix != null) {
492: return suffix;
493: } else {
494: return valueIfEmpty;
495: }
496: }
497:
498: StringBuffer buffer = new StringBuffer(299);
499:
500: boolean first = true;
501: do {
502:
503: // Get the name and value
504: String name = (String) names.next();
505: String value = properties.get(name);
506:
507: // If the value is null or an empty string, then output nothing
508: if (value == null || value.length() == 0) {
509: continue;
510: }
511:
512: // Append an ampersand, except for the first entry
513: if (!first) {
514: buffer.append('&');
515: } else {
516: first = false;
517: if (prefixIfNotEmpty != null) {
518: buffer.append(prefixIfNotEmpty);
519: }
520: }
521:
522: // Append the key and the value, separated by an equals sign
523: buffer.append(URLEncoding.encode(name));
524: buffer.append('=');
525: String encodedValue;
526: if (maxValueLength == -1
527: || value.length() <= maxValueLength) {
528: encodedValue = URLEncoding.encode(value);
529: } else {
530: encodedValue = URLEncoding.encode(value.substring(0,
531: maxValueLength))
532: + "...";
533: }
534: buffer.append(encodedValue);
535: } while (names.hasNext());
536:
537: if (suffix != null) {
538: buffer.append('&');
539: buffer.append(suffix);
540: }
541:
542: return buffer.toString();
543: }
544:
545: /**
546: * Compares a <code>PropertyReader</code> instance with another object for
547: * equality.
548: *
549: * @param pr
550: * the <code>PropertyReader</code>, can be <code>null</code>.
551: *
552: * @param toCompare
553: * the object to compare the <code>PropertyReader</code> with,
554: * can be <code>null</code>.
555: *
556: * @return
557: * <code>true</code> if the objects are considered to be equal,
558: * <code>false</code> if they are considered different.
559: *
560: * @since XINS 2.1
561: */
562: public static final boolean equals(PropertyReader pr,
563: Object toCompare) {
564:
565: // Test for identity equality
566: if (pr == toCompare) {
567: return true;
568: }
569:
570: // If either one is null, then they are not equal (otherwise they would
571: // both be null in which case they are identity equal)
572: if (pr == null || toCompare == null) {
573: return false;
574: }
575:
576: // The 2nd object must implement the PropertyReader interface
577: if (!(toCompare instanceof PropertyReader)) {
578: return false;
579: }
580:
581: // Size must be the same
582: PropertyReader pr2 = (PropertyReader) toCompare;
583: if (pr.size() != pr2.size()) {
584: return false;
585: }
586:
587: // Loop over all key/value pairs
588: Iterator keys = pr.getNames();
589: while (keys.hasNext()) {
590: String key = (String) keys.next();
591: String value1 = pr.get(key);
592: String value2 = pr2.get(key);
593: if (value1 == null && value2 != null) {
594: return false;
595: } else if (value1 != null && !value1.equals(value2)) {
596: return false;
597: }
598: }
599:
600: // No differences found
601: return true;
602: }
603:
604: /**
605: * Computes a hash code value for the specified <code>PropertyReader</code>
606: * object.
607: *
608: * @param pr
609: * the <code>PropertyReader</code> instance to compute a hash code value
610: * for, cannot be <code>null</code>.
611: *
612: * @return
613: * the hash code value.
614: *
615: * @throws NullPointerException
616: * if <code>pr == null</code>.
617: *
618: * @since XINS 2.1
619: */
620: public static final int hashCode(PropertyReader pr)
621: throws NullPointerException {
622:
623: int hash = 0;
624:
625: // Loop over all key/value pairs
626: Iterator keys = pr.getNames();
627: while (keys.hasNext()) {
628: String key = (String) keys.next();
629: String value = pr.get(key);
630:
631: // XOR the hash code value with the key string hash code
632: if (key != null) {
633: hash ^= key.hashCode();
634: }
635:
636: // XOR the hash code value with the key string hash code
637: if (value != null) {
638: hash ^= value.hashCode();
639: }
640: }
641:
642: return hash;
643: }
644: }
|