PropertiesConfiguration.java
/*
* Copyright (c) 2007-2025 MetaSolutions AB
*
* Licensed 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 org.entrystore.repository.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.entrystore.config.Config;
import org.entrystore.config.DurationStyle;
import java.awt.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
/**
* Wrapper around Java's Properties.
* Some methods have been simplified, others just wrapped.<br>
*
* <p>
* See the static methods of the class Configurations for wrappers around the
* Config interface, e.g., to get synchronized view of the object.
*
* <p>
* If a key maps only to one value, it is done the standard way:<br>
* <pre>key=value</pre>
*
* <p>
* If a key maps to multiple values, the key is numbered:<br>
* <pre>key.1=value1</pre>
* <pre>key.2=value2</pre>
*
* @author Hannes Ebner
* @version $Id$
* @see org.entrystore.config.Configurations
* @see org.entrystore.config.Config
*/
public class PropertiesConfiguration implements Config {
Log log = LogFactory.getLog(PropertiesConfiguration.class);
/**
* The main resource in this object. Contains the configuration.
*/
private final SortedProperties config;
private final PropertyChangeSupport pcs;
private final String configName;
private boolean modified = false;
/* Constructors */
/**
* Initializes the object with an empty Configuration.
*
* @param configName
* Name of the configuration (appears as comment in the
* configuration file).
*/
public PropertiesConfiguration(String configName) {
this.configName = configName;
config = new SortedProperties();
pcs = new PropertyChangeSupport(this);
}
/* Generic helpers */
/**
* Sets the modified status of this configuration.
*
* @param modified
* Status.
*/
private void setModified(boolean modified) {
this.modified = modified;
}
private void checkFirePropertyChange(String key, Object oldValue, Object newValue) {
if ((oldValue == null) && (newValue != null)) {
pcs.firePropertyChange(key, null, newValue);
} else if ((oldValue != null) && (!oldValue.equals(newValue))) {
pcs.firePropertyChange(key, oldValue, newValue);
}
}
/*
* List helpers
*/
private String numberedKey(String key, int number) {
return key + "." + number;
}
private int getPropertyValueCount(String key) {
int valueCount = 0;
if (config.containsKey(key)) {
valueCount = 1;
} else {
while (config.containsKey(numberedKey(key, valueCount + 1))) {
valueCount++;
}
}
return valueCount;
}
private synchronized void addPropertyValue(String key, Object value) {
int valueCount = getPropertyValueCount(key);
if ((valueCount == 1) && config.containsKey(key)) {
String oldValue = config.getProperty(key);
config.remove(key);
config.setProperty(numberedKey(key, 1), oldValue);
config.setProperty(numberedKey(key, 2), value.toString());
} else if (valueCount > 1){
config.setProperty(numberedKey(key, valueCount + 1), value.toString());
} else if (valueCount == 0) {
config.setProperty(key, value.toString());
}
}
private void addPropertyValues(String key, List values) {
addPropertyValues(key, values.iterator());
}
private synchronized void addPropertyValues(String key, Iterator it) {
while (it.hasNext()) {
addPropertyValue(key, it.next());
}
}
private synchronized List<String> getPropertyValues(String key) {
int valueCount = getPropertyValueCount(key);
List<String> result = new ArrayList<>();
if (valueCount == 1) {
String value = config.getProperty(key);
if (value == null) {
value = config.getProperty(numberedKey(key, 1));
}
if (value != null) {
result.add(value);
}
} else {
for (int i = 1; i <= valueCount; i++) {
result.add(config.getProperty(numberedKey(key, i)));
}
}
return result;
}
private synchronized void clearPropertyValues(String key) {
int valueCount = getPropertyValueCount(key);
if (valueCount > 1) {
for (int i = 1; i <= valueCount; i++) {
config.remove(numberedKey(key, i));
}
}
config.remove(key);
}
private void setPropertyValues(String key, List values) {
setPropertyValues(key, values.iterator());
}
private synchronized void setPropertyValues(String key, Iterator it) {
clearPropertyValues(key);
addPropertyValues(key, it);
}
/*
* Interface implementation
*/
/* Generic */
@Override
public void clear() {
config.clear();
setModified(true);
}
@Override
public boolean isEmpty() {
return config.isEmpty();
}
@Override
public boolean isModified() {
return modified;
}
@Override
public void load(URL configURL) throws IOException {
InputStreamReader isr = null;
try {
URL escapedURL = new URI(configURL.toString().replaceAll(" ", "%20")).toURL();
isr = new InputStreamReader(escapedURL.openStream(), StandardCharsets.UTF_8);
config.load(isr);
} catch (URISyntaxException e) {
log.error(e.getMessage());
} finally {
if (isr != null) {
isr.close();
}
}
}
@Override
public void save(URL configURL) throws IOException {
try {
String escapedURL = configURL.toString().replaceAll(" ", "%20");
URI url = new URI(escapedURL);
File file = new File(url);
OutputStreamWriter output = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8);
config.store(output, configName);
output.close();
} catch (URISyntaxException e) {
throw new IOException(e.getMessage());
}
setModified(false);
}
/* Property Change Listeners */
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
@Override
public void addPropertyChangeListener(String key, PropertyChangeListener listener) {
pcs.addPropertyChangeListener(key, listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(String key, PropertyChangeListener listener) {
pcs.removePropertyChangeListener(key, listener);
}
/* Properties / Set Values */
@Override
public void clearProperty(String key) {
int valueCount = getPropertyValueCount(key);
Object oldValue = null;
if (valueCount == 0) {
return;
} else if (valueCount == 1) {
oldValue = getString(key);
} else if (valueCount > 1) {
oldValue = getStringList(key);
}
clearPropertyValues(key);
setModified(true);
checkFirePropertyChange(key, oldValue, null);
}
@Override
public void addProperty(String key, Object value) {
addPropertyValue(key, value);
setModified(true);
pcs.firePropertyChange(key, null, value);
}
@Override
public void addProperties(String key, List values) {
addPropertyValues(key, values);
setModified(true);
pcs.firePropertyChange(key, null, values);
}
@Override
public void addProperties(String key, Iterator values) {
addPropertyValues(key, values);
setModified(true);
pcs.firePropertyChange(key, null, values);
}
@Override
public void setProperty(String key, Object value) {
String oldValue;
oldValue = getString(key);
config.setProperty(key, value.toString());
setModified(true);
checkFirePropertyChange(key, oldValue, value);
}
@Override
public void setProperties(String key, List values) {
List<String> oldValues = getStringList(key);
setPropertyValues(key, values);
setModified(true);
checkFirePropertyChange(key, oldValues, values);
}
@Override
public void setProperties(String key, Iterator values) {
List<String> oldValues = getStringList(key);
setPropertyValues(key, values);
setModified(true);
checkFirePropertyChange(key, oldValues, values);
}
/* Keys */
@Override
public boolean containsKey(String key) {
return config.containsKey(key);
}
@Override
public List<String> getKeyList() {
return getKeyList(null);
}
@Override
public List<String> getKeyList(String prefix) {
Enumeration keyIterator = config.propertyNames();
ArrayList<String> result = new ArrayList<>();
while (keyIterator.hasMoreElements()) {
String next = (String) keyIterator.nextElement();
if ((prefix != null) && !next.startsWith(prefix)) {
continue;
}
result.add(next);
}
return result;
}
/* Get Values */
@Override
public String getString(String key) {
return config.getProperty(key);
}
@Override
public String getString(String key, String defaultValue) {
return config.getProperty(key, defaultValue);
}
@Override
public List<String> getStringList(String key) {
return getPropertyValues(key);
}
@Override
public List<String> getStringList(String key, List<String> defaultValues) {
List<String> result = getPropertyValues(key);
if (result.isEmpty()) {
result = defaultValues;
}
return result;
}
@Override
public boolean getBoolean(String key) {
return getBoolean(key, false);
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
String strValue = config.getProperty(key);
if ("on".equalsIgnoreCase(strValue)) {
return true;
} else if ("off".equalsIgnoreCase(strValue)) {
return false;
}
if (strValue != null) {
return Boolean.parseBoolean(strValue);
} else {
return defaultValue;
}
}
@Override
public byte getByte(String key) {
String strValue = config.getProperty(key);
byte byteValue = 0;
if (strValue != null) {
byteValue = Byte.parseByte(strValue);
}
return byteValue;
}
@Override
public byte getByte(String key, byte defaultValue) {
String strValue = config.getProperty(key);
byte byteValue;
if (strValue != null) {
byteValue = Byte.parseByte(strValue);
} else {
byteValue = defaultValue;
}
return byteValue;
}
@Override
public double getDouble(String key) {
String strValue = config.getProperty(key);
double doubleValue = 0;
if (strValue != null) {
doubleValue = Double.parseDouble(strValue);
}
return doubleValue;
}
@Override
public double getDouble(String key, double defaultValue) {
String strValue = config.getProperty(key);
double doubleValue;
if (strValue != null) {
doubleValue = Double.parseDouble(strValue);
} else {
doubleValue = defaultValue;
}
return doubleValue;
}
@Override
public float getFloat(String key) {
String strValue = config.getProperty(key);
float floatValue = 0;
if (strValue != null) {
floatValue = Float.parseFloat(strValue);
}
return floatValue;
}
@Override
public float getFloat(String key, float defaultValue) {
String strValue = config.getProperty(key);
float floatValue;
if (strValue != null) {
floatValue = Float.parseFloat(strValue);
} else {
floatValue = defaultValue;
}
return floatValue;
}
@Override
public int getInt(String key) {
String strValue = config.getProperty(key);
int intValue = 0;
if (strValue != null) {
intValue = Integer.parseInt(strValue);
}
return intValue;
}
@Override
public int getInt(String key, int defaultValue) {
String strValue = config.getProperty(key);
int intValue;
if (strValue != null) {
intValue = Integer.parseInt(strValue);
} else {
intValue = defaultValue;
}
return intValue;
}
@Override
public long getLong(String key) {
String strValue = config.getProperty(key);
long longValue = 0;
if (strValue != null) {
longValue = Long.parseLong(strValue);
}
return longValue;
}
@Override
public long getLong(String key, long defaultValue) {
String strValue = config.getProperty(key);
long longValue;
if (strValue != null) {
longValue = Long.parseLong(strValue);
} else {
longValue = defaultValue;
}
return longValue;
}
@Override
public short getShort(String key) {
String strValue = config.getProperty(key);
short shortValue = 0;
if (strValue != null) {
shortValue = Short.parseShort(strValue);
}
return shortValue;
}
@Override
public short getShort(String key, short defaultValue) {
String strValue = config.getProperty(key);
short shortValue;
if (strValue != null) {
shortValue = Short.parseShort(strValue);
} else {
shortValue = defaultValue;
}
return shortValue;
}
@Override
public URI getURI(String key) {
try {
String uri = config.getProperty(key);
if (uri != null) {
return new URI(uri);
}
} catch (URISyntaxException ignored) {
}
return null;
}
@Override
public URI getURI(String key, URI defaultValue) {
URI result = getURI(key);
if (result == null) {
return defaultValue;
}
return result;
}
@Override
public URL getURL(String key) {
try {
String uri = config.getProperty(key);
if (uri != null) {
return new URI(uri).toURL();
}
} catch (URISyntaxException | MalformedURLException ignored) {
}
return null;
}
@Override
public URL getURL(String key, URL defaultValue) {
URL result = getURL(key);
if (result == null) {
return defaultValue;
}
return result;
}
@Override
public Color getColor(String key) {
Color result = null;
String value = getString(key);
if (value != null) {
try {
if (!value.startsWith("0x"))
result = Color.decode(value);
else {
int rgb = Long.decode(value).intValue();
result = new Color(rgb);
}
} catch (NumberFormatException ignored) {
}
}
return result;
}
@Override
public Color getColor(String key, Color defaultValue) {
Color result = getColor(key);
if (result == null) {
return defaultValue;
}
return result;
}
@Override
public Duration getDuration(String key) {
String durationString = config.getProperty(key);
if (durationString == null) {
return null;
}
return DurationStyle.detectAndParse(durationString);
}
@Override
public Duration getDuration(String key, Duration defaultValue) {
String durationString = config.getProperty(key);
if (durationString == null) {
return defaultValue;
}
return DurationStyle.detectAndParse(durationString);
}
@Override
public Duration getDuration(String key, String defaultValue) {
String durationString = config.getProperty(key);
if (durationString == null) {
return DurationStyle.detectAndParse(defaultValue);
}
return DurationStyle.detectAndParse(durationString);
}
@Override
public Duration getDuration(String key, long defaultValue) {
String durationString = config.getProperty(key);
if (durationString == null) {
return Duration.ofMillis(defaultValue);
}
return DurationStyle.detectAndParse(durationString);
}
@Override
public Properties getProperties() {
return this.config;
}
}