com.codename1.io.Properties Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.codename1.io;
import com.codename1.util.CaseInsensitiveOrder;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A {@code Properties} object is a {@code Hashtable} where the keys and values
* must be {@code String}s. Each property can have a default
* {@code Properties} list which specifies the default
* values to be used when a given key is not found in this {@code Properties}
* instance.
*
* Character Encoding
* Note that in some cases {@code Properties} uses ISO-8859-1 instead of UTF-8.
* ISO-8859-1 is only capable of representing a tiny subset of Unicode.
* Use either the {@code loadFromXML}/{@code storeToXML} methods (which use UTF-8 by
* default) or the {@code load}/{@code store} overloads that take
* an {@code OutputStreamWriter} (so you can supply a UTF-8 instance) instead.
*
* @see Hashtable
* @see java.lang.System#getProperties
*/
public class Properties extends HashMap {
/**
* The default values for keys not found in this {@code Properties}
* instance.
*/
protected Properties defaults;
private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3,
KEY_DONE = 4, IGNORE = 5;
/**
* Constructs a new {@code Properties} object.
*/
public Properties() {
}
/**
* Constructs a new {@code Properties} object using the specified default
* {@code Properties}.
*
* @param properties
* the default {@code Properties}.
*/
public Properties(Properties properties) {
defaults = properties;
}
private void dumpString(StringBuilder buffer, String string, boolean key) {
int i = 0;
if (!key && i < string.length() && string.charAt(i) == ' ') {
buffer.append("\\ ");
i++;
}
int slen = string.length();
for (; i < slen; i++) {
char ch = string.charAt(i);
switch (ch) {
case '\t':
buffer.append("\\t");
break;
case '\n':
buffer.append("\\n");
break;
case '\f':
buffer.append("\\f");
break;
case '\r':
buffer.append("\\r");
break;
default:
if ("\\#!=:".indexOf(ch) >= 0 || (key && ch == ' ')) {
buffer.append('\\');
}
if (ch >= ' ' && ch <= '~') {
buffer.append(ch);
} else {
String hex = Integer.toHexString(ch);
buffer.append("\\u");
int hlen = hex.length();
for (int j = 0; j < 4 - hlen; j++) {
buffer.append("0");
}
buffer.append(hex);
}
}
}
}
/**
* Searches for the property with the specified name. If the property is not
* found, the default {@code Properties} are checked. If the property is not
* found in the default {@code Properties}, {@code null} is returned.
*
* @param name
* the name of the property to find.
* @return the named property value, or {@code null} if it can't be found.
*/
public String getProperty(String name) {
Object result = super.get(name);
String property = result instanceof String ? (String) result : null;
if (property == null && defaults != null) {
property = defaults.getProperty(name);
}
return property;
}
/**
* Searches for the property with the specified name. If the property is not
* found, it looks in the default {@code Properties}. If the property is not
* found in the default {@code Properties}, it returns the specified
* default.
*
* @param name
* the name of the property to find.
* @param defaultValue
* the default value.
* @return the named property value.
*/
public String getProperty(String name, String defaultValue) {
Object result = super.get(name);
String property = result instanceof String ? (String) result : null;
if (property == null && defaults != null) {
property = defaults.getProperty(name);
}
if (property == null) {
return defaultValue;
}
return property;
}
/**
* Loads properties from the specified {@code InputStream}, assumed to be ISO-8859-1.
* See "Character Encoding".
*
* @param in the {@code InputStream}
* @throws IOException
*/
public synchronized void load(InputStream in) throws IOException {
if (in == null) {
throw new NullPointerException("in == null");
}
load(new InputStreamReader(in, "UTF-8"));
}
/**
* Loads properties from the specified {@code Reader}.
* The properties file is interpreted according to the following rules:
*
* - Empty lines are ignored.
* - Lines starting with either a "#" or a "!" are comment lines and are
* ignored.
* - A backslash at the end of the line escapes the following newline
* character ("\r", "\n", "\r\n"). If there's whitespace after the
* backslash it will just escape that whitespace instead of concatenating
* the lines. This does not apply to comment lines.
* - A property line consists of the key, the space between the key and
* the value, and the value. The key goes up to the first whitespace, "=" or
* ":" that is not escaped. The space between the key and the value contains
* either one whitespace, one "=" or one ":" and any amount of additional
* whitespace before and after that character. The value starts with the
* first character after the space between the key and the value.
* - Following escape sequences are recognized: "\ ", "\\", "\r", "\n",
* "\!", "\#", "\t", "\b", "\f", and "\uXXXX" (unicode character).
*
*
* @param in the {@code Reader}
* @throws IOException
* @since 1.6
*/
@SuppressWarnings("fallthrough")
public synchronized void load(Reader in) throws IOException {
if (in == null) {
throw new NullPointerException("in == null");
}
int mode = NONE, unicode = 0, count = 0;
char nextChar, buf[] = new char[40];
int offset = 0, keyLength = -1, intVal;
boolean firstChar = true;
Reader br = in;
while (true) {
intVal = br.read();
if (intVal == -1) {
break;
}
nextChar = (char) intVal;
if (offset == buf.length) {
char[] newBuf = new char[buf.length * 2];
System.arraycopy(buf, 0, newBuf, 0, offset);
buf = newBuf;
}
if (mode == UNICODE) {
int digit = Character.digit(nextChar, 16);
if (digit >= 0) {
unicode = (unicode << 4) + digit;
if (++count < 4) {
continue;
}
} else if (count <= 4) {
throw new IllegalArgumentException("Invalid Unicode sequence: illegal character");
}
mode = NONE;
buf[offset++] = (char) unicode;
if (nextChar != '\n') {
continue;
}
}
if (mode == SLASH) {
mode = NONE;
switch (nextChar) {
case '\r':
mode = CONTINUE; // Look for a following \n
continue;
case '\n':
mode = IGNORE; // Ignore whitespace on the next line
continue;
case 'b':
nextChar = '\b';
break;
case 'f':
nextChar = '\f';
break;
case 'n':
nextChar = '\n';
break;
case 'r':
nextChar = '\r';
break;
case 't':
nextChar = '\t';
break;
case 'u':
mode = UNICODE;
unicode = count = 0;
continue;
}
} else {
switch (nextChar) {
case '#':
case '!':
if (firstChar) {
while (true) {
intVal = br.read();
if (intVal == -1) {
break;
}
nextChar = (char) intVal;
if (nextChar == '\r' || nextChar == '\n') {
break;
}
}
continue;
}
break;
case '\n':
if (mode == CONTINUE) { // Part of a \r\n sequence
mode = IGNORE; // Ignore whitespace on the next line
continue;
}
// fall into the next case
case '\r':
mode = NONE;
firstChar = true;
if (offset > 0 || (offset == 0 && keyLength == 0)) {
if (keyLength == -1) {
keyLength = offset;
}
String temp = new String(buf, 0, offset);
put(temp.substring(0, keyLength), temp
.substring(keyLength));
}
keyLength = -1;
offset = 0;
continue;
case '\\':
if (mode == KEY_DONE) {
keyLength = offset;
}
mode = SLASH;
continue;
case ':':
case '=':
if (keyLength == -1) { // if parsing the key
mode = NONE;
keyLength = offset;
continue;
}
break;
}
if (nextChar == ' ' || nextChar == '\n' || nextChar == '\r' || nextChar == '\t') {
if (mode == CONTINUE) {
mode = IGNORE;
}
// if key length == 0 or value length == 0
if (offset == 0 || offset == keyLength || mode == IGNORE) {
continue;
}
if (keyLength == -1) { // if parsing the key
mode = KEY_DONE;
continue;
}
}
if (mode == IGNORE || mode == CONTINUE) {
mode = NONE;
}
}
firstChar = false;
if (mode == KEY_DONE) {
keyLength = offset;
mode = NONE;
}
buf[offset++] = nextChar;
}
if (mode == UNICODE && count <= 4) {
throw new IllegalArgumentException("Invalid Unicode sequence: expected format \\uxxxx");
}
if (keyLength == -1 && offset > 0) {
keyLength = offset;
}
if (keyLength >= 0) {
String temp = new String(buf, 0, offset);
String key = temp.substring(0, keyLength);
String value = temp.substring(keyLength);
if (mode == SLASH) {
value += "\u0000";
}
put(key, value);
}
}
/**
* Returns all of the property names (keys) in this {@code Properties} object.
*/
public Enumeration> propertyNames() {
Hashtable