0001: /*
0002: * Copyright (C) 2002-2007 Stephen Ostermiller
0003: * http://ostermiller.org/contact.pl?regarding=Java+Utilities
0004: *
0005: * Copyright (C) 2003 Carlo Magnaghi <software at tecnosoft dot net>
0006: *
0007: * This program is free software; you can redistribute it and/or modify
0008: * it under the terms of the GNU General Public License as published by
0009: * the Free Software Foundation; either version 2 of the License, or
0010: * (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * See COPYING.TXT for details.
0018: */
0019: package com.Ostermiller.util;
0020:
0021: import java.io.*;
0022: import java.util.*;
0023:
0024: /**
0025: * The Properties class represents a persistent set of properties. The
0026: * Properties can be saved to a stream or loaded from a stream. Each key and
0027: * its corresponding value in the property list is a string.
0028: * More information about this class is available from <a target="_top" href=
0029: * "http://ostermiller.org/utils/UberProperties.html">ostermiller.org</a>.
0030: * <p>
0031: * A property list can contain another property list as its "defaults"; this
0032: * second property list is searched if the property key is not found in the
0033: * original property list.
0034: * <p>
0035: * When saving properties to a stream or loading them from a stream, the ISO
0036: * 8859-1 character encoding is used. For characters that cannot be directly
0037: * represented in this encoding, Unicode escapes are used; however, only a
0038: * single 'u' character is allowed in an escape sequence. The native2ascii tool
0039: * can be used to convert property files to and from other character encodings.
0040: * <p>
0041: * Unlike the java.util.Properties, UberProperties does not inherit from
0042: * java.util.Hashtable, so Objects other than strings cannot be stored in it.
0043: * Also, comments from a files are preserved, and there can be several
0044: * properties for a given name.
0045: * <p>
0046: * This class is not synchronized, so it should not be used in a
0047: * multi-threaded environment without external synchronization.
0048: * <p>
0049: * The file format that UberProperties uses is as follows:
0050: * <blockquote>
0051: * The file is assumed to be using the ISO 8859-1 character encoding. All of the
0052: * comment lines (starting with a '#' or '!') at the beginning of the file before the
0053: * first line that is not a comment, are the comment associated with the file.
0054: * After that, each comment will be associated with the next property. If there
0055: * is more than one property with the same name, the first comment will be the
0056: * only one that is loaded.
0057: * <p>
0058: * Every property occupies one line of the input stream. Each line is terminated
0059: * by a line terminator (\n or \r or \r\n).
0060: * <p>
0061: * A line that contains only whitespace or whose first non-whitespace character
0062: * is an ASCII # or ! is ignored (thus, # or ! indicate comment lines).
0063: * <p>
0064: * Every line other than a blank line or a comment line describes one property
0065: * to be added to the table (except that if a line ends with \, then the
0066: * following line, if it exists, is treated as a continuation line,
0067: * as described below). The key consists of all the characters in the line
0068: * starting with the first non-whitespace character and up to, but not
0069: * including, the first ASCII =, :, or whitespace character. All of the key
0070: * termination characters may be included in the key by preceding them with a \.
0071: * Any whitespace after the key is skipped; if the first non-whitespace
0072: * character after the key is = or :, then it is ignored and any whitespace
0073: * characters after it are also skipped. All remaining characters on the line
0074: * become part of the associated element string. Within the element string, the
0075: * ASCII escape sequences \t, \n, \r, \\, \", \', \ (a backslash and a space),
0076: * and \\uxxxx are recognized and converted to single characters. Moreover, if
0077: * the last character on the line is \, then the next line is treated as a
0078: * continuation of the current line; the \ and line terminator are simply
0079: * discarded, and any leading whitespace characters on the continuation line are
0080: * also discarded and are not part of the element string.
0081: * <p>
0082: * As an example, each of the following four lines specifies the key "Truth"
0083: * and the associated element value "Beauty":<br>
0084: * <pre>Truth = Beauty
0085: * Truth:Beauty
0086: * Truth :Beauty</pre>
0087: * <p>
0088: * As another example, the following three lines specify a single property:<br>
0089: * <pre>fruits apple, banana, pear, \
0090: * cantaloupe, watermelon, \
0091: * kiwi, mango</pre>
0092: * <p>
0093: * The key is "fruits" and the associated element is:<br>
0094: * "apple, banana, pear, cantaloupe, watermelon, kiwi, mango"<br>
0095: * Note that a space appears before each \ so that a space will appear after
0096: * each comma in the final result; the \, line terminator, and leading
0097: * whitespace on the continuation line are merely discarded and are not replaced
0098: * by one or more other characters.
0099: * <p>
0100: * As a third example, the line:<br>
0101: * cheeses<br>
0102: * specifies that the key is "cheeses" and the associated element is the empty
0103: * string.
0104: * </blockquote>
0105: *
0106: * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
0107: * @since ostermillerutils 1.00.00
0108: */
0109: public class UberProperties {
0110:
0111: /**
0112: * A hash map that contains all the properties.
0113: * This should never be null, but may be empty.
0114: * This should hold objects of type Property.
0115: *
0116: * @since ostermillerutils 1.00.00
0117: */
0118: private HashMap<String, Property> properties = new HashMap<String, Property>();
0119:
0120: /**
0121: * Comment for this set of properties.
0122: * This may be either null or empty.
0123: *
0124: * @since ostermillerutils 1.00.00
0125: */
0126: private String comment = null;
0127:
0128: /**
0129: * The object type that goes in the HashMap.
0130: *
0131: * @since ostermillerutils 1.00.00
0132: */
0133: private class Property {
0134:
0135: /**
0136: * List of values for this property.
0137: * This should never be null or empty.
0138: *
0139: * @since ostermillerutils 1.00.00
0140: */
0141: private ArrayList<String> list;
0142:
0143: /**
0144: * Comment for this set of properties.
0145: * This may be either null or empty.
0146: *
0147: * @since ostermillerutils 1.00.00
0148: */
0149: private String comment = null;
0150:
0151: /**
0152: * Set the comment associated with this property.
0153: *
0154: * @param comment the comment for this property, or null to clear.
0155: *
0156: * @since ostermillerutils 1.00.00
0157: */
0158: public void setComment(String comment) {
0159: this .comment = comment;
0160: }
0161:
0162: /**
0163: * Get the comment associated with this property.
0164: *
0165: * @return comment for this property, or null if none is set.
0166: *
0167: * @since ostermillerutils 1.00.00
0168: */
0169: public String getComment() {
0170: return this .comment;
0171: }
0172:
0173: /**
0174: * Construct a new property with the given value.
0175: *
0176: * @param value initial value for this property.
0177: *
0178: * @since ostermillerutils 1.00.00
0179: */
0180: public Property(String value) {
0181: list = new ArrayList<String>(1);
0182: add(value);
0183: }
0184:
0185: /**
0186: * Construct a new property with the given values.
0187: *
0188: * @param values initial values for this property.
0189: *
0190: * @since ostermillerutils 1.00.00
0191: */
0192: public Property(String[] values) {
0193: list = new ArrayList<String>(values.length);
0194: add(values);
0195: }
0196:
0197: /**
0198: * Set this property to have this single value.
0199: *
0200: * @param value lone value for this property.
0201: *
0202: * @since ostermillerutils 1.00.00
0203: */
0204: public void set(String value) {
0205: list.clear();
0206: add(value);
0207: }
0208:
0209: /**
0210: * Set this property to have only these values.
0211: *
0212: * @param values lone values for this property.
0213: *
0214: * @since ostermillerutils 1.00.00
0215: */
0216: public void set(String[] values) {
0217: list.clear();
0218: add(values);
0219: }
0220:
0221: /**
0222: * Add this value to the list of values for this property.
0223: *
0224: * @param value another value for this property.
0225: *
0226: * @since ostermillerutils 1.00.00
0227: */
0228: public void add(String value) {
0229: list.add(value);
0230: }
0231:
0232: /**
0233: * Add these values to the list of values for this property.
0234: *
0235: * @param values other values for this property.
0236: *
0237: * @since ostermillerutils 1.00.00
0238: */
0239: public void add(String[] values) {
0240: list.ensureCapacity(list.size() + values.length);
0241: for (String element : values) {
0242: add(element);
0243: }
0244: }
0245:
0246: /**
0247: * Get the last value for this property.
0248: *
0249: * @return the last value.
0250: *
0251: * @since ostermillerutils 1.00.00
0252: */
0253: public String getValue() {
0254: return list.get(list.size() - 1);
0255: }
0256:
0257: /**
0258: * Get all the values for this property.
0259: *
0260: * @return a list of all the values.
0261: *
0262: * @since ostermillerutils 1.00.00
0263: */
0264: public String[] getValues() {
0265: return list.toArray(new String[list.size()]);
0266: }
0267: }
0268:
0269: /**
0270: * Creates an empty property list with no default values.
0271: *
0272: * @since ostermillerutils 1.00.00
0273: */
0274: public UberProperties() {
0275: // Create empty properties
0276: }
0277:
0278: /**
0279: * Creates an empty property list with the specified defaults.
0280: *
0281: * @param defaults the defaults.
0282: * @throws NullPointerException if defaults is null.
0283: *
0284: * @since ostermillerutils 1.00.00
0285: */
0286: public UberProperties(UberProperties defaults) {
0287: merge(defaults);
0288: }
0289:
0290: /**
0291: * Put all the properties from the defaults in this.
0292: * Calling this from a constructor will clone (deep)
0293: * the default properties.
0294: *
0295: * @since ostermillerutils 1.00.00
0296: */
0297: private void merge(UberProperties defaults) {
0298: setComment(defaults.getComment());
0299: String[] names = defaults.propertyNames();
0300: for (String element : names) {
0301: setProperties(element, defaults.getProperties(element));
0302: setComment(element, defaults.getComment(element));
0303: }
0304: }
0305:
0306: /**
0307: * Test to see if a property with the given name exists.
0308: *
0309: * @param name the name of the property.
0310: * @return true if the property existed and was removed, false if it did not exist.
0311: * @throws NullPointerException if name is null.
0312: *
0313: * @since ostermillerutils 1.00.00
0314: */
0315: public boolean contains(String name) {
0316: if (name == null)
0317: throw new NullPointerException();
0318: return properties.containsKey(name);
0319: }
0320:
0321: /**
0322: * Remove any property with the given name.
0323: *
0324: * @param name the name of the property.
0325: * @return true if the property existed and was removed, false if it did not exist.
0326: * @throws NullPointerException if name is null.
0327: *
0328: * @since ostermillerutils 1.00.00
0329: */
0330: public boolean remove(String name) {
0331: if (!contains(name))
0332: return false;
0333: properties.remove(name);
0334: return true;
0335: }
0336:
0337: /**
0338: * Replaces all properties of the given name with
0339: * a single property with the given value.
0340: *
0341: * @param name the name of the property.
0342: * @param value the value of the property, or null to remove it.
0343: * @throws NullPointerException if name is null.
0344: *
0345: * @since ostermillerutils 1.00.00
0346: */
0347: public void setProperty(String name, String value) {
0348: if (name == null)
0349: throw new NullPointerException();
0350: if (value == null) {
0351: properties.remove(name);
0352: } else {
0353: Property property;
0354: if (properties.containsKey(name)) {
0355: property = properties.get(name);
0356: property.set(value);
0357: } else {
0358: property = new Property(value);
0359: properties.put(name, property);
0360: }
0361: }
0362: }
0363:
0364: /**
0365: * Replaces all properties of the given name with
0366: * properties with the given values.
0367: *
0368: * @param name the name of the property.
0369: * @param values for the property.
0370: * @throws NullPointerException if name is null.
0371: * @throws NullPointerException if values is null.
0372: * @throws IllegalArgumentException if values is empty.
0373: *
0374: * @since ostermillerutils 1.00.00
0375: */
0376: public void setProperties(String name, String[] values) {
0377: if (name == null)
0378: throw new NullPointerException();
0379: if (values.length == 0)
0380: throw new IllegalArgumentException();
0381: Property property;
0382: if (properties.containsKey(name)) {
0383: property = properties.get(name);
0384: property.set(values);
0385: } else {
0386: property = new Property(values);
0387: properties.put(name, property);
0388: }
0389: }
0390:
0391: /**
0392: * Replaces all properties of the given name with
0393: * a single property with the given value.
0394: *
0395: * @param name the name of the property.
0396: * @param value the value of the property or null to remove it.
0397: * @param comment the comment for the property, or null to remove it.
0398: * @throws NullPointerException if name is null.
0399: * @throws NullPointerException if comment is null.
0400: *
0401: * @since ostermillerutils 1.00.00
0402: */
0403: public void setProperty(String name, String value, String comment) {
0404: if (name == null)
0405: throw new NullPointerException();
0406: if (value == null) {
0407: properties.remove(name);
0408: } else {
0409: setProperty(name, value);
0410: setComment(name, comment);
0411: }
0412: }
0413:
0414: /**
0415: * Replaces all properties of the given name with
0416: * properties with the given values.
0417: *
0418: * @param name the name of the property.
0419: * @param values value of the property.
0420: * @param comment the comment for the property, or null to remove it.
0421: * @throws NullPointerException if name is null.
0422: * @throws NullPointerException if values is null.
0423: * @throws IllegalArgumentException if values is empty.
0424: *
0425: * @since ostermillerutils 1.00.00
0426: */
0427: public void setProperties(String name, String[] values,
0428: String comment) {
0429: if (name == null)
0430: throw new NullPointerException();
0431: if (values.length == 0)
0432: throw new IllegalArgumentException();
0433: setProperties(name, values);
0434: setComment(name, comment);
0435: }
0436:
0437: /**
0438: * Set the comment on the property of the given name.
0439: * The property must exist before this method is called.
0440: *
0441: * @param name the name of the property.
0442: * @param comment the comment for the property.
0443: * @param comment the comment for the property, or null to remove it.
0444: * @throws NullPointerException if name is null.
0445: * @throws IllegalArgumentException if name is not a known key.
0446: *
0447: * @since ostermillerutils 1.00.00
0448: */
0449: private void setComment(String name, String comment) {
0450: if (name == null)
0451: throw new NullPointerException();
0452: if (!properties.containsKey(name))
0453: throw new IllegalArgumentException();
0454: (properties.get(name)).setComment(comment);
0455: }
0456:
0457: /**
0458: * Adds a value to the list of properties with the
0459: * given name.
0460: *
0461: * @param name the name of the property.
0462: * @param value the values for the property, or null to remove.
0463: * @param comment the comment for the property, or null to remove it.
0464: * @throws NullPointerException if name is null.
0465: * @throws NullPointerException if value is null.
0466: *
0467: * @since ostermillerutils 1.00.00
0468: */
0469: public void addProperty(String name, String value, String comment) {
0470: if (name == null)
0471: throw new NullPointerException();
0472: if (value == null)
0473: throw new NullPointerException();
0474: addProperty(name, value);
0475: setComment(name, comment);
0476: }
0477:
0478: /**
0479: * Adds the values to the list of properties with the
0480: * given name.
0481: *
0482: * @param name the name of the property.
0483: * @param values the values for the property.
0484: * @param comment the comment for the property, or null to remove it.
0485: * @throws NullPointerException if name is null.
0486: * @throws NullPointerException if values is null.
0487: *
0488: * @since ostermillerutils 1.00.00
0489: */
0490: public void addProperties(String name, String[] values,
0491: String comment) {
0492: if (name == null)
0493: throw new NullPointerException();
0494: if (values == null)
0495: throw new NullPointerException();
0496: addProperties(name, values);
0497: setComment(name, comment);
0498: }
0499:
0500: /**
0501: * Adds a value to the list of properties with the
0502: * given name.
0503: *
0504: * @param name the name of the property.
0505: * @param value the values for the property.
0506: * @throws NullPointerException if name is null.
0507: * @throws NullPointerException if value is null.
0508: *
0509: * @since ostermillerutils 1.00.00
0510: */
0511: public void addProperty(String name, String value) {
0512: if (name == null)
0513: throw new NullPointerException();
0514: if (value == null)
0515: throw new NullPointerException();
0516: Property property;
0517: if (properties.containsKey(name)) {
0518: property = properties.get(name);
0519: property.add(value);
0520: } else {
0521: property = new Property(value);
0522: properties.put(name, property);
0523: }
0524: }
0525:
0526: /**
0527: * Adds the values to the list of properties with the
0528: * given name.
0529: *
0530: * @param name the name of the property.
0531: * @param values the values for the property.
0532: * @throws NullPointerException if name is null.
0533: * @throws NullPointerException if values is null.
0534: *
0535: * @since ostermillerutils 1.00.00
0536: */
0537: public void addProperties(String name, String[] values) {
0538: if (name == null)
0539: throw new NullPointerException();
0540: if (values == null)
0541: throw new NullPointerException();
0542: Property property;
0543: if (properties.containsKey(name)) {
0544: property = properties.get(name);
0545: property.add(values);
0546: } else {
0547: property = new Property(values);
0548: properties.put(name, property);
0549: }
0550: }
0551:
0552: private static int hexDigitValue(char c) {
0553: switch (c) {
0554: case '0':
0555: return 0;
0556: case '1':
0557: return 1;
0558: case '2':
0559: return 2;
0560: case '3':
0561: return 3;
0562: case '4':
0563: return 4;
0564: case '5':
0565: return 5;
0566: case '6':
0567: return 6;
0568: case '7':
0569: return 7;
0570: case '8':
0571: return 8;
0572: case '9':
0573: return 9;
0574: case 'a':
0575: case 'A':
0576: return 10;
0577: case 'b':
0578: case 'B':
0579: return 11;
0580: case 'c':
0581: case 'C':
0582: return 12;
0583: case 'd':
0584: case 'D':
0585: return 13;
0586: case 'e':
0587: case 'E':
0588: return 14;
0589: case 'f':
0590: case 'F':
0591: return 15;
0592: default:
0593: return -1;
0594: }
0595: }
0596:
0597: private static String unescape(String s) {
0598: StringBuffer sb = new StringBuffer(s.length());
0599: for (int i = 0; i < s.length(); i++) {
0600: char c = s.charAt(i);
0601: if (c == '\\') {
0602: i++;
0603: if (i < s.length()) {
0604: c = s.charAt(i);
0605: switch (c) {
0606: case 'n': {
0607: sb.append('\n');
0608: }
0609: break;
0610: case 'r': {
0611: sb.append('\r');
0612: }
0613: break;
0614: case 't': {
0615: sb.append('\t');
0616: }
0617: break;
0618: case 'f': {
0619: sb.append('\f');
0620: }
0621: break;
0622: case 'u': {
0623: boolean foundUnicode = false;
0624: if (i + 4 < s.length()) {
0625: int unicodeValue = 0;
0626: for (int j = 3; unicodeValue >= 0 && j >= 0; j--) {
0627: int val = hexDigitValue(s.charAt(i
0628: + (4 - j)));
0629: if (val == -1) {
0630: unicodeValue = -1;
0631: } else {
0632: unicodeValue |= (val << (j << 2));
0633: }
0634: }
0635: if (unicodeValue >= 0) {
0636: i += 4;
0637: foundUnicode = true;
0638: sb.append((char) unicodeValue);
0639: }
0640: }
0641: if (!foundUnicode)
0642: sb.append(c);
0643: }
0644: break;
0645: default: {
0646: sb.append(c);
0647: }
0648: break;
0649: }
0650: }
0651: } else {
0652: sb.append(c);
0653: }
0654: }
0655: return sb.toString();
0656: }
0657:
0658: /**
0659: * Load these properties from a user file with default properties
0660: * from a system resource.
0661: * <p>
0662: * Example:
0663: * <pre>load(
0664: * new String(){".java","tld","company","package","component.properties"}
0665: * "tld/company/package/component.properties",
0666: * )</pre>
0667: * This will load the properties file relative to the classpath as the
0668: * defaults and the file <%userhome%>/.java/tld/company/package/component.properties
0669: * if the file exists. The .java directory is recommended as it is a common,
0670: * possibly hidden, directory in the users home directory commonly used by
0671: * Java programs.
0672: *
0673: * This method is meant to be used with the save(String systemResource) method
0674: * which will save modified properties back to the user directory.
0675: *
0676: * @param userFile array of Strings representing a path and file name relative to the user home directory.
0677: * @param systemResource name relative to classpath of default properties, or null to ignore.
0678: * @throws IOException if an error occurs when reading.
0679: * @throws NullPointerException if userFile is null.
0680: * @throws IllegalArgumentException if userFile is empty.
0681: *
0682: * @since ostermillerutils 1.00.00
0683: */
0684: public void load(String[] userFile, String systemResource)
0685: throws IOException {
0686: int length = userFile.length;
0687: if (userFile.length == 0)
0688: throw new IllegalArgumentException();
0689: InputStream in = ClassLoader
0690: .getSystemResourceAsStream(systemResource);
0691: if (in == null)
0692: throw new FileNotFoundException(systemResource);
0693: if (systemResource != null)
0694: load(in);
0695: File f = new File(System.getProperty("user.home"));
0696: for (int i = 0; f.exists() && i < length; i++) {
0697: f = new File(f, userFile[i]);
0698: }
0699: if (f.exists())
0700: load(new FileInputStream(f));
0701: }
0702:
0703: /**
0704: * Add the properties from the input stream to this
0705: * UberProperties.
0706: *
0707: * @param in InputStream containing properties.
0708: * @param add whether parameters should add to parameters with the same name or replace them.
0709: * @throws IOException if an error occurs when reading.
0710: *
0711: * @since ostermillerutils 1.00.00
0712: */
0713: public void load(InputStream in, boolean add) throws IOException {
0714: PropertiesLexer lex = new PropertiesLexer(
0715: new InputStreamReader(in, "ISO-8859-1"));
0716: PropertiesToken t;
0717: HashSet<String> names = new HashSet<String>();
0718: StringBuffer comment = new StringBuffer();
0719: boolean foundComment = false;
0720: StringBuffer name = new StringBuffer();
0721: StringBuffer value = new StringBuffer();
0722: boolean atStart = true;
0723: String lastSeparator = null;
0724: while ((t = lex.getNextToken()) != null) {
0725: if (t.getID() == PropertiesToken.COMMENT) {
0726: int start = 1;
0727: String commentText = t.getContents();
0728: if (commentText.startsWith("# "))
0729: start = 2;
0730: comment.append(commentText.substring(start, commentText
0731: .length()));
0732: comment.append("\n");
0733: lex.getNextToken();
0734: foundComment = true;
0735: } else if (t.getID() == PropertiesToken.NAME) {
0736: if (atStart) {
0737: setComment(comment.toString());
0738: comment.setLength(0);
0739: atStart = false;
0740: }
0741: name.append(t.getContents());
0742: } else if (t.getID() == PropertiesToken.VALUE) {
0743: if (atStart) {
0744: setComment(comment.toString());
0745: comment.setLength(0);
0746: atStart = false;
0747: }
0748: value.append(t.getContents());
0749: } else if (t.getID() == PropertiesToken.SEPARATOR) {
0750: lastSeparator = t.getContents();
0751: } else if (t.getID() == PropertiesToken.END_LINE_WHITE_SPACE) {
0752: if (atStart) {
0753: setComment(comment.toString());
0754: comment.setLength(0);
0755: atStart = false;
0756: }
0757: String stName = unescape(name.toString());
0758: String stValue = unescape(value.toString());
0759: if (lastSeparator != null || stName.length() > 0
0760: || stValue.length() > 0) {
0761: if (add || names.contains(stName)) {
0762: addProperty(stName, stValue);
0763: } else {
0764: setProperty(stName, stValue);
0765: names.add(stName);
0766: }
0767: if (foundComment)
0768: setComment(stName, unescape(comment.toString()));
0769: }
0770: comment.setLength(0);
0771: name.setLength(0);
0772: value.setLength(0);
0773: foundComment = false;
0774: lastSeparator = null;
0775: }
0776: }
0777: }
0778:
0779: /**
0780: * Add the properties from the input stream to this
0781: * UberProperties.
0782: * <p>
0783: * Properties that are found replace any properties that
0784: * were there before.
0785: *
0786: * @param in InputStream containing properties.
0787: * @throws IOException if an error occurs when reading.
0788: *
0789: * @since ostermillerutils 1.00.00
0790: */
0791: public void load(InputStream in) throws IOException {
0792: load(in, false);
0793: }
0794:
0795: /**
0796: * Save these properties from a user file.
0797: * <p>
0798: * Example:
0799: * <pre>save(
0800: * new String(){"tld","company","package","component.properties"}
0801: * )</pre>
0802: * This will save the properties file relative to the user directory:
0803: * <%userhome%>/tld/company/package/component.properties
0804: * Directories will be created as needed.
0805: *
0806: * @param userFile array of Strings representing a path and file name relative to the user home directory.
0807: * @throws IOException if an error occurs when reading.
0808: * @throws NullPointerException if userFile is null.
0809: * @throws IllegalArgumentException if userFile is empty.
0810: *
0811: * @since ostermillerutils 1.00.00
0812: */
0813: public void save(String[] userFile) throws IOException {
0814: int length = userFile.length;
0815: if (length == 0)
0816: throw new IllegalArgumentException();
0817: File f = new File(System.getProperty("user.home"));
0818: for (int i = 0; i < length; i++) {
0819: f = new File(f, userFile[i]);
0820: if (i == length - 2 && !f.exists()) {
0821: f.mkdirs();
0822: }
0823: }
0824: OutputStream out = new FileOutputStream(f);
0825: save(out);
0826: out.close();
0827: }
0828:
0829: /**
0830: * Save these properties to the given stream.
0831: *
0832: * @param out OutputStream to which these properties should be written.
0833: * @throws IOException if an error occurs when writing.
0834: *
0835: * @since ostermillerutils 1.00.00
0836: */
0837: public void save(OutputStream out) throws IOException {
0838: writeComment(out, comment);
0839: out.write('\n');
0840: String[] names = propertyNames();
0841: Arrays.sort(names);
0842: for (String element : names) {
0843: writeComment(out, getComment(element));
0844: String[] values = getProperties(element);
0845: for (String element2 : values) {
0846: writeProperty(out, element, element2);
0847: }
0848: }
0849: out.flush();
0850: }
0851:
0852: private static void writeProperty(OutputStream out, String name,
0853: String value) throws IOException {
0854: writeEscapedISO88591(out, name, TYPE_NAME);
0855: out.write('=');
0856: writeEscapedISO88591(out, value, TYPE_VALUE);
0857: out.write('\n');
0858:
0859: }
0860:
0861: private static void writeComment(OutputStream out, String comment)
0862: throws IOException {
0863: if (comment != null) {
0864: java.util.StringTokenizer tok = new java.util.StringTokenizer(
0865: comment, "\r\n");
0866: while (tok.hasMoreTokens()) {
0867: out.write('#');
0868: out.write(' ');
0869: writeEscapedISO88591(out, tok.nextToken(), TYPE_COMMENT);
0870: out.write('\n');
0871: }
0872: }
0873: }
0874:
0875: private static final int TYPE_COMMENT = 0;
0876: private static final int TYPE_NAME = 1;
0877: private static final int TYPE_VALUE = 2;
0878:
0879: private static void writeEscapedISO88591(OutputStream out,
0880: String s, int type) throws IOException {
0881: for (int i = 0; i < s.length(); i++) {
0882: int c = s.charAt(i);
0883: if (c < 0x100) {
0884: boolean escape = false;
0885: if (c == '\r' || c == '\n' || c == '\\') {
0886: escape = true;
0887: } else if (c == ' ' || c == '\t' || c == '\f') {
0888: if (type == TYPE_NAME) {
0889: escape = true;
0890: } else if (type == TYPE_VALUE
0891: && (i == 0 || i == s.length() - 1)) {
0892: escape = true;
0893: }
0894: } else if (type == TYPE_NAME && (c == '=' || c == ':')) {
0895: escape = true;
0896: }
0897: if (escape) {
0898: switch (c) {
0899: case '\n': {
0900: switch (type) {
0901: case TYPE_COMMENT: {
0902: out.write('\n');
0903: out.write('#');
0904: out.write(' ');
0905: }
0906: break;
0907: case TYPE_NAME: {
0908: out.write('\\');
0909: out.write('n');
0910: out.write('\\');
0911: out.write('\n');
0912: out.write('\t');
0913: }
0914: break;
0915: case TYPE_VALUE: {
0916: out.write('\\');
0917: out.write('n');
0918: out.write('\\');
0919: out.write('\n');
0920: out.write('\t');
0921: out.write('\t');
0922: }
0923: break;
0924: }
0925: }
0926: break;
0927: case '\\': {
0928: out.write('\\');
0929: out.write('\\');
0930: }
0931: break;
0932: case '\r': {
0933: out.write('\\');
0934: out.write('r');
0935: }
0936: break;
0937: case '\t': {
0938: out.write('\\');
0939: out.write('t');
0940: }
0941: break;
0942: case '\f': {
0943: out.write('\\');
0944: out.write('f');
0945: }
0946: break;
0947: default: {
0948: out.write('\\');
0949: out.write((byte) c);
0950: }
0951: break;
0952: }
0953: } else {
0954: out.write((byte) c);
0955: }
0956: } else {
0957: out.write('\\');
0958: out.write('u');
0959: out.write(StringHelper.prepad(Integer.toHexString(c),
0960: 4, '0').getBytes("ISO-8859-1"));
0961: }
0962: }
0963: }
0964:
0965: /**
0966: * Get the first property with the given name.
0967: * If the property is not specified in this UberProperties
0968: * but it is in the default UberProperties, the default is
0969: * used. If no default is found, null is returned.
0970: *
0971: * @param name Parameter name
0972: * @return the first value of this property, or null if the property does not exist.
0973: *
0974: * @since ostermillerutils 1.00.00
0975: */
0976: public String getProperty(String name) {
0977: String value = null;
0978: if (properties.containsKey(name)) {
0979: value = (properties.get(name)).getValue();
0980: }
0981: return value;
0982: }
0983:
0984: /**
0985: * Get the first property with the given name.
0986: * If the property is not specified in this UberProperties
0987: * but it is in the default UberProperties, the default
0988: * UberProperties is consulted, otherwise, the supplied
0989: * default is used.
0990: *
0991: * @param name Parameter name
0992: * @param defaultValue Value to use when property not present
0993: * @return the first value of this property.
0994: *
0995: * @since ostermillerutils 1.00.00
0996: */
0997: public String getProperty(String name, String defaultValue) {
0998: String value = getProperty(name);
0999: if (value == null)
1000: value = defaultValue;
1001: return value;
1002: }
1003:
1004: /**
1005: * Get the values for a property.
1006: * Properties returned in the same order in which
1007: * they were added.
1008: * <p>
1009: * If the property is not specified in this UberProperties
1010: * but it is in the default UberProperties, the default is
1011: * used. If no default is found, null is returned.
1012: *
1013: * @param name Parameter name
1014: * @return all the values associated with the given key, or null if the property does not exist.
1015: *
1016: * @since ostermillerutils 1.00.00
1017: */
1018: public String[] getProperties(String name) {
1019: String[] values = null;
1020: if (properties.containsKey(name)) {
1021: values = (properties.get(name)).getValues();
1022: }
1023: return values;
1024: }
1025:
1026: /**
1027: * Get the values for a property.
1028: * Properties returned in the same order in which
1029: * they were added.
1030: * <p>
1031: * If the property is not specified in this UberProperties
1032: * but it is in the default UberProperties, the default
1033: * UberProperties is consulted, otherwise, the supplied
1034: * defaults are used.
1035: *
1036: * @param name Parameter name
1037: * @param defaultValues Values to use when property not present
1038: * @return all the values associated with the given key, or null if the property does not exist.
1039: *
1040: * @since ostermillerutils 1.00.00
1041: */
1042: public String[] getProperties(String name, String[] defaultValues) {
1043: String[] values = getProperties(name);
1044: if (values == null)
1045: values = defaultValues;
1046: return values;
1047: }
1048:
1049: /**
1050: * Get the comment associated with this property.
1051: * <p>
1052: * If the property is not specified in this UberProperties
1053: * but it is in the default UberProperties, the default is
1054: * used. If no default is found, null is returned.
1055: *
1056: * @param name Parameter name
1057: * @return the comment for this property, or null if there is no comment or the property does not exist.
1058: *
1059: * @since ostermillerutils 1.00.00
1060: */
1061: public String getComment(String name) {
1062: String comment = null;
1063: if (properties.containsKey(name)) {
1064: comment = (properties.get(name)).getComment();
1065: }
1066: return comment;
1067: }
1068:
1069: /**
1070: * Returns an enumeration of all the keys in this property list, including
1071: * distinct keys in the default property list if a key of the same name has
1072: * not already been found from the main properties list.
1073: *
1074: * @return an enumeration of all the keys in this property list, including the keys in the default property list.
1075: *
1076: * @since ostermillerutils 1.00.00
1077: */
1078: public String[] propertyNames() {
1079: Set<String> names = properties.keySet();
1080: return names.toArray(new String[names.size()]);
1081: }
1082:
1083: /**
1084: * Set the comment associated with this set of properties.
1085: *
1086: * @param comment the comment for entire set of properties, or null to clear.
1087: *
1088: * @since ostermillerutils 1.00.00
1089: */
1090: public void setComment(String comment) {
1091: this .comment = comment;
1092: }
1093:
1094: /**
1095: * Get the comment associated with this set of properties.
1096: *
1097: * @return comment for entire set of properties, or null if there is no comment.
1098: *
1099: * @since ostermillerutils 1.00.00
1100: */
1101: public String getComment() {
1102: return this .comment;
1103: }
1104:
1105: /**
1106: * Get the number of unique names for properties stored
1107: * in this UberProperties.
1108: *
1109: * @return number of names.
1110: *
1111: * @since ostermillerutils 1.00.00
1112: */
1113: public int getPropertyNameCount() {
1114: return properties.keySet().size();
1115: }
1116:
1117: /**
1118: * Save these properties to a string.
1119: *
1120: * @return Serialized String version of these properties.
1121: *
1122: * @since ostermillerutils 1.02.23
1123: */
1124: @Override
1125: public String toString() {
1126: ByteArrayOutputStream out = new ByteArrayOutputStream();
1127: try {
1128: this .save(out);
1129: } catch (IOException iox) {
1130: throw new Error(
1131: "IO constructed on memory, this shouldn't happen.",
1132: iox);
1133: }
1134: String s = null;
1135: try {
1136: s = new String(out.toByteArray(), "ISO-8859-1");
1137: } catch (UnsupportedEncodingException uee) {
1138: throw new Error("ISO-8859-1 should be recognized.", uee);
1139: }
1140: return s;
1141: }
1142: }
|